mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-12 07:59:02 +02:00
Merge origin/main and advance release to v1.84.1.0
Preserve the design interoperability release and clarify ship publication sequencing under frontier evaluation. Co-authored-by: OpenAI Codex <noreply@openai.com>
This commit is contained in:
+28
-2
@@ -134,6 +134,17 @@ CONFIG_HEADER='# gstack configuration — edit freely, changes take effect on ne
|
||||
# # /document-release skip the outside-voice step entirely.
|
||||
# # An invalid value is REJECTED (existing value preserved) so
|
||||
# # a typo cannot silently turn paid Codex calls on or off.
|
||||
# design_detector_install_prompted: false
|
||||
# # true once you answered the one-time offer from the
|
||||
# # design skills to download the impeccable engine with
|
||||
# # "never ask again"; flip back to false to be asked again.
|
||||
# design_detector: auto # Deterministic design pre-pass through a user-installed
|
||||
# # impeccable engine (/design-review, /review, /ship,
|
||||
# # /design-html). auto = use the engine when the probe
|
||||
# # finds one (gstack never installs or downloads it);
|
||||
# # off = no probe, no scan, no hint, no /impeccable
|
||||
# # handoff lines. An invalid value is REJECTED (existing
|
||||
# # value preserved) so a typo cannot silently disable it.
|
||||
# gstack_contributor: false # true = file field reports when gstack misbehaves
|
||||
# skip_eng_review: false # true = skip eng review gate in /ship (not recommended)
|
||||
#
|
||||
@@ -161,6 +172,8 @@ lookup_default() {
|
||||
checkpoint_push) echo "false" ;;
|
||||
explain_level) echo "default" ;;
|
||||
codex_reviews) echo "enabled" ;;
|
||||
design_detector) echo "auto" ;; # auto | off — impeccable engine pre-pass in the design skills
|
||||
design_detector_install_prompted) echo "false" ;; # true once the user answered the one-time engine install offer with "never ask again"
|
||||
gstack_contributor) echo "false" ;;
|
||||
skip_eng_review) echo "false" ;;
|
||||
workspace_root) echo "$HOME/conductor/workspaces" ;;
|
||||
@@ -429,6 +442,19 @@ case "${1:-}" in
|
||||
echo "Error: codex_reviews '$VALUE' not recognized. Valid values: enabled, disabled. Existing value left unchanged." >&2
|
||||
exit 1
|
||||
fi
|
||||
# design_detector_install_prompted records "never ask again" for the one-time
|
||||
# engine install offer. Rejecting a typo keeps the offer from silently coming
|
||||
# back (or never coming back) because of a mistyped value.
|
||||
if [ "$KEY" = "design_detector_install_prompted" ] && [ "$VALUE" != "true" ] && [ "$VALUE" != "false" ]; then
|
||||
echo "Error: design_detector_install_prompted '$VALUE' not recognized. Valid values: true, false. Existing value left unchanged." >&2
|
||||
exit 1
|
||||
fi
|
||||
# design_detector gates a third-party binary the user installed. Reject a typo
|
||||
# rather than coerce it: "of" must not silently re-enable or disable the scan.
|
||||
if [ "$KEY" = "design_detector" ] && [ "$VALUE" != "auto" ] && [ "$VALUE" != "off" ]; then
|
||||
echo "Error: design_detector '$VALUE' not recognized. Valid values: auto, off. Existing value left unchanged." >&2
|
||||
exit 1
|
||||
fi
|
||||
# cross_project_learnings: empty get is the first-run prompt sentinel.
|
||||
# Skills enable only on the literal "true". A typo must not persist — that
|
||||
# keeps the feature off and suppresses the prompt. Reject, like
|
||||
@@ -475,7 +501,7 @@ case "${1:-}" in
|
||||
skill_prefix checkpoint_mode checkpoint_push explain_level \
|
||||
codex_reviews gstack_contributor skip_eng_review workspace_root \
|
||||
artifacts_sync_mode artifacts_sync_mode_prompted plan_tune_hooks \
|
||||
timeline_stop_hook memorable_recall; do
|
||||
timeline_stop_hook design_detector design_detector_install_prompted memorable_recall; do
|
||||
VALUE=$(read_config_value "$KEY" || true)
|
||||
SOURCE="default"
|
||||
if [ -n "$VALUE" ]; then
|
||||
@@ -492,7 +518,7 @@ case "${1:-}" in
|
||||
skill_prefix checkpoint_mode checkpoint_push explain_level \
|
||||
codex_reviews gstack_contributor skip_eng_review workspace_root \
|
||||
artifacts_sync_mode artifacts_sync_mode_prompted plan_tune_hooks \
|
||||
timeline_stop_hook memorable_recall; do
|
||||
timeline_stop_hook design_detector design_detector_install_prompted memorable_recall; do
|
||||
printf ' %-24s %s\n' "$KEY:" "$(lookup_default "$KEY")"
|
||||
done
|
||||
;;
|
||||
|
||||
Executable
+1008
File diff suppressed because it is too large
Load Diff
Executable
+126
@@ -0,0 +1,126 @@
|
||||
#!/usr/bin/env bun
|
||||
/**
|
||||
* gstack-design-md — inspect, convert, and read DESIGN.md in the open format.
|
||||
*
|
||||
* bun --no-env-file run ~/.claude/skills/gstack/bin/gstack-design-md.ts check [DESIGN.md]
|
||||
* bun --no-env-file run ~/.claude/skills/gstack/bin/gstack-design-md.ts convert [DESIGN.md] [--write]
|
||||
* bun --no-env-file run ~/.claude/skills/gstack/bin/gstack-design-md.ts tokens [DESIGN.md]
|
||||
* bun --no-env-file run ~/.claude/skills/gstack/bin/gstack-design-md.ts mark <spec|legacy-keep> [DESIGN.md]
|
||||
*
|
||||
* check DESIGN_MD_FORMAT: spec | legacy | unknown | missing (+ DESIGN_MD_REASON for unknown),
|
||||
* DESIGN_MD_MARKER: spec | legacy-keep | none. Exit 0.
|
||||
* convert Legacy → spec (lib/design-md.ts convertLegacy). Prints the result; with --write,
|
||||
* backs the original up to DESIGN.md.legacy.bak and writes temp+rename. Refuses an
|
||||
* ambiguous file (DESIGN_MD_CONVERT_REFUSED, exit 2) and a non-legacy one (exit 1).
|
||||
* tokens Flat token map as JSON ({"colors.primary": "#F59E0B", ...}); {path} refs resolved;
|
||||
* invalid refs listed under "errors" (DESIGN_MD_TOKEN_REF_INVALID). Exit 0.
|
||||
* mark Persist the user's one-time format choice inside the file: a file that opens with
|
||||
* front matter gets a YAML comment on line 2, any other file an HTML comment on
|
||||
* line 1. A text-level splice: every other byte is untouched. Refuses a choice that
|
||||
* contradicts the file (spec on a non-spec file, legacy-keep on a spec file), exit 2.
|
||||
*
|
||||
* Exit 3 + DESIGN_MD_INTERNAL_ERROR is a gstack bug. YAML errors never propagate: a file whose
|
||||
* front matter does not parse is `unknown` with a reason.
|
||||
*/
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { SENTINEL } from '../lib/design-detect-contract';
|
||||
import { atomicWriteSync } from '../lib/fs-atomic';
|
||||
import {
|
||||
parseDesignMd, detectFormat, convertLegacy, renderDesignMd, tokensFlat, insertMarker,
|
||||
type DesignMdDoc, type FormatChoice, FORMAT_CHOICES, DesignMdEditRefused } from '../lib/design-md';
|
||||
|
||||
/** The file itself, through any symlink (a `DESIGN.md -> docs/DESIGN.md` layout must edit the target, never replace the link). */
|
||||
function resolveFile(arg?: string): string {
|
||||
const p = path.resolve(arg ?? 'DESIGN.md');
|
||||
try { return fs.realpathSync(p); } catch { return p; }
|
||||
}
|
||||
|
||||
function load(file: string): { text: string; doc: DesignMdDoc } | null {
|
||||
try { const text = fs.readFileSync(file, 'utf-8'); return { text, doc: parseDesignMd(text) }; } catch { return null; }
|
||||
}
|
||||
|
||||
export function main(argv = process.argv.slice(2)): number {
|
||||
const verb = argv[0] ?? '';
|
||||
const flags = new Set(argv.filter(a => a.startsWith('--')));
|
||||
const positional = argv.slice(1).filter(a => !a.startsWith('--'));
|
||||
|
||||
switch (verb) {
|
||||
case 'check': {
|
||||
const file = resolveFile(positional[0]);
|
||||
const loaded = load(file);
|
||||
const { format, reason } = detectFormat(loaded?.doc ?? null);
|
||||
process.stdout.write(`${SENTINEL.DESIGN_MD_FORMAT}: ${format}\n`);
|
||||
if (reason) process.stdout.write(`${SENTINEL.DESIGN_MD_REASON}: ${reason}\n`);
|
||||
process.stdout.write(`${SENTINEL.DESIGN_MD_MARKER}: ${loaded?.doc.marker ?? 'none'}\n`);
|
||||
return 0;
|
||||
}
|
||||
case 'convert': {
|
||||
const file = resolveFile(positional[0]);
|
||||
const loaded = load(file);
|
||||
const { format, code, reason } = detectFormat(loaded?.doc ?? null);
|
||||
if (code === 'ambiguous') {
|
||||
process.stderr.write(`${SENTINEL.DESIGN_MD_CONVERT_REFUSED}: ${reason}\n`);
|
||||
return 2;
|
||||
}
|
||||
if (format !== 'legacy' || !loaded) {
|
||||
process.stderr.write(`${SENTINEL.DESIGN_MD_FORMAT}: ${format}${reason ? ` (${reason})` : ''}; convert only accepts a legacy gstack DESIGN.md\n`);
|
||||
return 1;
|
||||
}
|
||||
let out: string;
|
||||
try {
|
||||
out = renderDesignMd(convertLegacy(loaded.doc), { emitFrontmatter: true });
|
||||
} catch (err) {
|
||||
if (err instanceof DesignMdEditRefused) { process.stderr.write(`${SENTINEL.DESIGN_MD_CONVERT_REFUSED}: ${err.message.replace(/^[A-Z_]+: /, '')}\n`); return 2; }
|
||||
throw err;
|
||||
}
|
||||
if (flags.has('--write')) {
|
||||
fs.writeFileSync(`${file}.legacy.bak`, loaded.text);
|
||||
atomicWriteSync(file, out);
|
||||
process.stdout.write(`${SENTINEL.DESIGN_MD_FORMAT}: spec\n${SENTINEL.DESIGN_MD_WRITTEN}: ${file}\n${SENTINEL.DESIGN_MD_BACKUP}: ${file}.legacy.bak\n`);
|
||||
} else {
|
||||
process.stdout.write(out);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
case 'tokens': {
|
||||
const file = resolveFile(positional[0]);
|
||||
const loaded = load(file);
|
||||
const flat = tokensFlat(loaded?.doc.frontmatter ?? null);
|
||||
process.stdout.write(JSON.stringify({ file, format: detectFormat(loaded?.doc ?? null).format, ...flat }, null, 2) + '\n');
|
||||
for (const e of flat.errors) process.stderr.write(e + '\n');
|
||||
return 0;
|
||||
}
|
||||
case 'mark': {
|
||||
const choice = positional[0] as FormatChoice | undefined;
|
||||
if (!(FORMAT_CHOICES as readonly string[]).includes(choice)) {
|
||||
process.stderr.write(`usage: gstack-design-md.ts mark <${FORMAT_CHOICES.join('|')}> [DESIGN.md]\n`);
|
||||
return 2;
|
||||
}
|
||||
const file = resolveFile(positional[1]);
|
||||
const loaded = load(file);
|
||||
if (!loaded) { process.stdout.write(`${SENTINEL.DESIGN_MD_FORMAT}: missing\n`); return 1; }
|
||||
const { format } = detectFormat(loaded.doc);
|
||||
if ((choice === 'spec' && format !== 'spec') || (choice === 'legacy-keep' && format === 'spec')) {
|
||||
process.stderr.write(`${SENTINEL.DESIGN_MD_CONVERT_REFUSED}: mark ${choice} contradicts the file's format (${format}); file unchanged\n`);
|
||||
return 2;
|
||||
}
|
||||
atomicWriteSync(file, insertMarker(loaded.text, choice));
|
||||
process.stdout.write(`${SENTINEL.DESIGN_MD_MARKER}: ${choice}\n`);
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
process.stderr.write(`usage: gstack-design-md.ts check [file] | convert [file] [--write] | tokens [file] | mark <${FORMAT_CHOICES.join('|')}> [file]\n`);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.main) {
|
||||
try {
|
||||
process.exitCode = main();
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
process.stderr.write(`${SENTINEL.DESIGN_MD_INTERNAL_ERROR}: ${e?.name ?? 'Error'}: ${String(e?.message ?? e).slice(0, 300)}\n`);
|
||||
process.exitCode = 3;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user