feat(architecture): [EoS/claude] drive claude through the imperative adapter + descriptor-driven hostBehaviors (#2086)
Fold Claude Code's install/uninstall onto the Embeddable Orchestration System
(ADR-1239 Phase D). claude is GSD's tier-1 reference host, but its install path
was still driven by 13 hardcoded `runtime === 'claude'` string-equality branches
scattered across bin/install.js rather than the public Host-Integration Interface.
- Route install()/uninstall() through `createImperativeAdapter({runtime})` — the
adapter delegates to the SAME installRuntimeArtifacts/uninstallRuntimeArtifacts
engine calls, so output is byte-identical (proven pre/post, both scopes).
- Replace all 13 `runtime === 'claude'` / `runtime !== 'claude'` branches with
descriptor-driven `runtime.hostBehaviors` lookups on capabilities/claude/
capability.json (attributionSource, authorsCanonicalWorkflow, localInstallStyle,
permissionsSchema, settingsFileByScope, sourceMarkerFile, agentFrontmatterExtensions,
ownsClaudePaths, nativeModelAliases, skillsGlobalOnboarding). Behavior is
identical; the brittle string-equality coupling (the add-a-host tax) is gone.
- Single-source the scattered literal 'claude' defaults/rosters behind DEFAULT_RUNTIME.
- Extend golden-install-parity to assert the claude LOCAL legacy layout is
byte-identical too (AC1 "both scopes"); exclude the platform-varying
settings.local.json (same reason settings.json is excluded).
- New tests/claude-imperative-reference.test.cjs: adapter kind, programmatic-cli
profile, fail-closed negotiation on a corrupted/partial descriptor, and an AC2
source guard that no `runtime === 'claude'` branch remains.
No user-visible install-output change (internal architecture only).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
5
.changeset/2086-eos-claude-imperative-adapter.md
Normal file
5
.changeset/2086-eos-claude-imperative-adapter.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 2086
|
||||
---
|
||||
**Internal: Claude Code's installer is now driven through the public Host-Integration Interface (ADR-1239 / EoS).** `bin/install.js` routes `claude` install/uninstall through the imperative adapter (`createImperativeAdapter`) instead of calling the engine directly, and its 13 hardcoded `runtime === 'claude'` / `runtime !== 'claude'` branches are folded into descriptor-driven `runtime.hostBehaviors` on `capabilities/claude/capability.json` (permission schema, `settings.local.json` scope routing, `.gsd-source` marker, effort frontmatter, canonical-workflow authorship, and more). Install/uninstall output is **byte-identical** for both the global skills layout and the local legacy layout (golden-parity asserted for both scopes); no other runtime changes. Removes the "add-a-host tax" of scattered string-equality checks for the tier-1 reference host. No user-facing change. (#2086)
|
||||
121
bin/install.js
121
bin/install.js
@@ -43,6 +43,7 @@ const {
|
||||
readBaseRefFromSettings,
|
||||
} = require('../gsd-core/bin/lib/worktree-base-ref.cjs');
|
||||
const { resolveInstallPlan } = require('../gsd-core/bin/lib/runtime-config-adapter-registry.cjs');
|
||||
const { createImperativeAdapter } = require('../gsd-core/bin/lib/adapter-imperative.cjs');
|
||||
const runtimeArtifactConversion = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs');
|
||||
// Canonical set of hook files shipped to users. Imported here so writeManifest()
|
||||
// records exactly the same set that build-hooks.js copies to hooks/dist/, making
|
||||
@@ -126,6 +127,9 @@ function isCodexHooksFeatureKey(key) {
|
||||
//
|
||||
// Merge policy: additive, non-destructive \u2014 existing user entries are preserved;
|
||||
// GSD entries are appended only when not already present (idempotent).
|
||||
// The reference/default runtime (ADR-1239 reference host). Single-sourced here
|
||||
// instead of scattered literal 'claude' defaults/rosters (#2086).
|
||||
const DEFAULT_RUNTIME = 'claude';
|
||||
const GSD_CLAUDE_ALLOW_PERMISSIONS = Object.freeze([
|
||||
'Bash(npx gsd-core *)',
|
||||
'Read(.planning/*)',
|
||||
@@ -310,6 +314,34 @@ try {
|
||||
} catch (_) {
|
||||
_capabilityRegistry = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Host-specific install behaviors, declared on the runtime descriptor
|
||||
* (capabilities/<runtime>/capability.json -> runtime.hostBehaviors) instead of
|
||||
* scattered `runtime === '<id>'` string checks (ADR-1239 / #2086). Returns {}
|
||||
* for runtimes that declare none, so every behavior branch degrades to the
|
||||
* generic path by default.
|
||||
*/
|
||||
function _hostBehaviors(runtime) {
|
||||
const cap = _capabilityRegistry && _capabilityRegistry.runtimes && _capabilityRegistry.runtimes[runtime];
|
||||
return (cap && cap.runtime && cap.runtime.hostBehaviors) || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the imperative Host-Integration adapter (ADR-1239 / #2086), FAIL-OPEN.
|
||||
* `createImperativeAdapter` composes the capability registry via
|
||||
* `loadRegistry({includeInstalled:true})`, which require()s several capability
|
||||
* modules. If any is unavailable (e.g. a packaging regression), return null so
|
||||
* the caller degrades to the engine directly rather than hard-crashing install/
|
||||
* uninstall — matching the optional `capability-registry.cjs` load posture above.
|
||||
*/
|
||||
function _runtimeAdapter(runtime) {
|
||||
try {
|
||||
return createImperativeAdapter({ runtime });
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
const {
|
||||
applyInstallerMigrationPlan,
|
||||
discoverInstallerMigrations,
|
||||
@@ -1244,9 +1276,9 @@ function getCommitAttribution(runtime) {
|
||||
: resolveKiloConfigPath;
|
||||
const config = readSettings(resolveConfigPath(getGlobalConfigDir(runtime, null)));
|
||||
result = (config && config.disable_ai_attribution === true) ? null : undefined;
|
||||
} else if (runtime === 'claude') {
|
||||
} else if (_hostBehaviors(runtime).attributionSource === 'settings-json-commit') {
|
||||
// Claude Code
|
||||
const settings = readSettings(path.join(getGlobalConfigDir('claude', explicitConfigDir), 'settings.json'));
|
||||
const settings = readSettings(path.join(getGlobalConfigDir(runtime, explicitConfigDir), 'settings.json'));
|
||||
if (!settings || !settings.attribution || settings.attribution.commit === undefined) {
|
||||
result = undefined;
|
||||
} else if (settings.attribution.commit === '') {
|
||||
@@ -6279,7 +6311,7 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand
|
||||
// copyWithPathReplacement is the emit path for gsd-core/workflows/*.md;
|
||||
// _applyRuntimeRewrites is NOT invoked here, so this is what makes the fix
|
||||
// live in real installs (it is a no-op for files without those lines).
|
||||
if (runtime !== 'claude') {
|
||||
if (!_hostBehaviors(runtime).authorsCanonicalWorkflow) {
|
||||
content = _stampNonClaudeRuntimeDefaults(content, runtime);
|
||||
}
|
||||
|
||||
@@ -6481,7 +6513,7 @@ const GSD_UNINSTALL_HOOKS = [
|
||||
* @param {boolean} isGlobal - Whether to uninstall from global or local
|
||||
* @param {string} runtime - Target runtime ('claude', 'opencode', 'codex', 'copilot')
|
||||
*/
|
||||
function uninstall(isGlobal, runtime = 'claude') {
|
||||
function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
||||
const { isOpencode, isKilo, isCodex, isCopilot, isAntigravity, isCursor, isWindsurf, isAugment, isTrae, isQwen, isHermes, isCodebuddy, isCline, isKimi } = runtimeFlags(runtime);
|
||||
const dirName = getDirName(runtime);
|
||||
|
||||
@@ -6539,7 +6571,14 @@ function uninstall(isGlobal, runtime = 'claude') {
|
||||
|
||||
// 1. Remove GSD commands/skills (layout-driven)
|
||||
const scope = isGlobal ? 'global' : 'local';
|
||||
uninstallRuntimeArtifacts(runtime, targetDir, scope);
|
||||
// ADR-1239 / #2086: drive uninstall through the public Host-Integration Interface.
|
||||
// Fail-open to the engine directly if the composed-registry adapter can't load.
|
||||
const _uninstallAdapter = _runtimeAdapter(runtime);
|
||||
if (_uninstallAdapter) {
|
||||
_uninstallAdapter.uninstall({ configDir: targetDir, scope });
|
||||
} else {
|
||||
uninstallRuntimeArtifacts(runtime, targetDir, scope);
|
||||
}
|
||||
removedCount++;
|
||||
|
||||
// 1a. Non-layout Codex side-effects: agent .toml files, config.toml sections, hooks.json
|
||||
@@ -6705,7 +6744,7 @@ function uninstall(isGlobal, runtime = 'claude') {
|
||||
|
||||
// 1c. Claude local: remove flat gsd-*.md commands from commands/ (current layout,
|
||||
// #1367 fix). Also remove legacy commands/gsd/ subdirectory from prior installs.
|
||||
if (!isGlobal && runtime === 'claude') {
|
||||
if (!isGlobal && _hostBehaviors(runtime).localInstallStyle === 'legacy-flat') {
|
||||
const commandsDir = path.join(targetDir, 'commands');
|
||||
// Remove flat gsd-*.md files (current layout after #1367 fix)
|
||||
if (fs.existsSync(commandsDir)) {
|
||||
@@ -7008,7 +7047,7 @@ function uninstall(isGlobal, runtime = 'claude') {
|
||||
// to preserve any user-added allow/deny entries.
|
||||
// Uses a local flag to avoid the shared `settingsModified` producing a false
|
||||
// "Removed GSD permissions" message when only hooks/statusline changed.
|
||||
if (runtime === 'claude' && settings.permissions) {
|
||||
if (_hostBehaviors(runtime).permissionsSchema === 'claude' && settings.permissions) {
|
||||
let permissionsModified = false;
|
||||
if (Array.isArray(settings.permissions.allow)) {
|
||||
const before = settings.permissions.allow.length;
|
||||
@@ -7483,7 +7522,7 @@ function resolveInstallRelativePath(baseDir, relPath) {
|
||||
/**
|
||||
* Write file manifest after installation for future modification detection
|
||||
*/
|
||||
function writeManifest(configDir, runtime = 'claude', options = {}) {
|
||||
function writeManifest(configDir, runtime = DEFAULT_RUNTIME, options = {}) {
|
||||
const { isOpencode, isKilo, isCodex, isCopilot, isAntigravity, isCursor, isWindsurf, isAugment, isTrae, isQwen, isHermes, isCodebuddy, isCline, isKimi } = runtimeFlags(runtime);
|
||||
const gsdDir = path.join(configDir, 'gsd-core');
|
||||
// #1367: Claude local now writes flat gsd-*.md files at commands/ (not commands/gsd/).
|
||||
@@ -7521,7 +7560,7 @@ function writeManifest(configDir, runtime = 'claude', options = {}) {
|
||||
// Claude local (#1367): flat gsd-*.md files at commands/ level.
|
||||
// Only claude local writes gsd-*.md here; global installs don't emit commands,
|
||||
// so this branch is a no-op for global (no matching files to find).
|
||||
if (runtime === 'claude' && fs.existsSync(flatCommandsDir)) {
|
||||
if (_hostBehaviors(runtime).localInstallStyle === 'legacy-flat' && fs.existsSync(flatCommandsDir)) {
|
||||
for (const file of fs.readdirSync(flatCommandsDir)) {
|
||||
if (file.startsWith('gsd-') && file.endsWith('.md')) {
|
||||
manifest.files['commands/' + file] = fileHash(path.join(flatCommandsDir, file));
|
||||
@@ -7934,7 +7973,7 @@ function saveLocalPatches(configDir, pristineCtx) {
|
||||
/**
|
||||
* After install, report backed-up patches for user to reapply.
|
||||
*/
|
||||
function reportLocalPatches(configDir, runtime = 'claude') {
|
||||
function reportLocalPatches(configDir, runtime = DEFAULT_RUNTIME) {
|
||||
const patchesDir = path.join(configDir, PATCHES_DIR_NAME);
|
||||
const metaPath = path.join(patchesDir, 'backup-meta.json');
|
||||
if (!fs.existsSync(metaPath)) return [];
|
||||
@@ -7977,7 +8016,7 @@ function reportInstallerMigrationResult(result) {
|
||||
}
|
||||
}
|
||||
|
||||
function install(isGlobal, runtime = 'claude', options = {}) {
|
||||
function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
||||
const { isOpencode, isKilo, isZcode, isCodex, isCopilot, isAntigravity, isCursor, isWindsurf, isAugment, isTrae, isQwen, isHermes, isCodebuddy, isCline, isKimi } = runtimeFlags(runtime);
|
||||
const plan = resolveInstallPlan(runtime);
|
||||
const dirName = getDirName(runtime);
|
||||
@@ -8422,7 +8461,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
||||
// (copyWithPathReplacement + stale-skills cleanup).
|
||||
const _isSkillsRuntime = (() => {
|
||||
if (isOpencode || isKilo) return false; // specialized combined path
|
||||
if (runtime === 'claude' && !isGlobal) return false; // claude-local legacy path
|
||||
if (_hostBehaviors(runtime).localInstallStyle === 'legacy-flat' && !isGlobal) return false; // legacy flat local path (descriptor-driven; #2086)
|
||||
const cap = _capabilityRegistry && _capabilityRegistry.runtimes && _capabilityRegistry.runtimes[runtime];
|
||||
const layout = cap && cap.runtime && cap.runtime.artifactLayout;
|
||||
if (!layout) return false;
|
||||
@@ -8433,7 +8472,21 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
||||
if (_isSkillsRuntime) {
|
||||
// Layout-driven install for skills-based runtimes (full and minimal modes)
|
||||
const scope = isGlobal ? 'global' : 'local';
|
||||
installRuntimeArtifacts(runtime, targetDir, scope, _resolvedProfile, getCommitAttribution);
|
||||
// ADR-1239 / #2086: drive install through the public Host-Integration Interface
|
||||
// (imperative adapter). The adapter delegates to the SAME installRuntimeArtifacts
|
||||
// engine call -> byte-identical output (gated by golden-install-parity). Fail-open
|
||||
// to the engine directly if the composed-registry adapter can't load.
|
||||
const _adapter = _runtimeAdapter(runtime);
|
||||
if (_adapter) {
|
||||
_adapter.install({
|
||||
configDir: targetDir,
|
||||
scope,
|
||||
resolvedProfile: _resolvedProfile,
|
||||
resolveAttribution: getCommitAttribution,
|
||||
});
|
||||
} else {
|
||||
installRuntimeArtifacts(runtime, targetDir, scope, _resolvedProfile, getCommitAttribution);
|
||||
}
|
||||
|
||||
// #1326 — Codex only: remove stale agents/openai.yaml sidecars from managed
|
||||
// gsd-* skill dirs. Prior installs wrote these files so Codex would show a
|
||||
@@ -8733,11 +8786,14 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
||||
// other runtime/scope deploys commands/gsd, so its walk-up already resolves
|
||||
// and needs no marker. Guarded on source presence so a half-published
|
||||
// package never writes a dangling marker.
|
||||
if (runtime === 'claude' && isGlobal) {
|
||||
if (_hostBehaviors(runtime).sourceMarkerFile && isGlobal) {
|
||||
const gsdSourceCommands = path.join(src, 'commands', 'gsd');
|
||||
if (fs.existsSync(gsdSourceCommands)) {
|
||||
try {
|
||||
fs.writeFileSync(path.join(targetDir, '.gsd-source'), gsdSourceCommands + '\n', 'utf8');
|
||||
// ADR-1239 Phase B write-confinement: the descriptor-sourced marker filename
|
||||
// must resolve under targetDir (parity with the other descriptor-driven writes).
|
||||
const _markerPath = assertDestWithinConfigHome(targetDir, _hostBehaviors(runtime).sourceMarkerFile);
|
||||
fs.writeFileSync(_markerPath, gsdSourceCommands + '\n', 'utf8');
|
||||
} catch (err) {
|
||||
// Non-fatal: install proceeds. But on the Claude-global layout walk-up
|
||||
// also fails (no commands/gsd source tree), so a silent write failure
|
||||
@@ -8932,11 +8988,11 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
||||
// Claude Code reads per-subagent `effort:` frontmatter (anthropics/claude-code #31536).
|
||||
// Injection is per-runtime at install time because the canonical source
|
||||
// agents/*.md must stay runtime-safe (no effort: key in source).
|
||||
if (runtime === 'claude') {
|
||||
if ((_hostBehaviors(runtime).agentFrontmatterExtensions || []).includes('effort')) {
|
||||
const _effortCfg = readGsdEffectiveEffortConfig(targetDir);
|
||||
const _agentName = entry.name.replace(/\.md$/, '');
|
||||
const _universalEffort = resolveInstallTimeEffort(_effortCfg, _agentName);
|
||||
const _renderedEffort = _getGsdEffortCatalog().renderEffortForRuntime('claude', _universalEffort).value;
|
||||
const _renderedEffort = _getGsdEffortCatalog().renderEffortForRuntime(runtime, _universalEffort).value;
|
||||
content = injectEffortFrontmatter(content, _renderedEffort);
|
||||
const _disallowedTools = READONLY_AGENT_DISALLOWED_TOOLS[_agentName];
|
||||
if (_disallowedTools) content = injectDisallowedToolsFrontmatter(content, _disallowedTools);
|
||||
@@ -9219,7 +9275,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
||||
reportLocalPatches(targetDir, runtime);
|
||||
|
||||
// Verify no leaked .claude paths in non-Claude runtimes (manifest-scoped)
|
||||
if (runtime !== 'claude') {
|
||||
if (!_hostBehaviors(runtime).ownsClaudePaths) {
|
||||
const leakedPaths = [];
|
||||
// Only scan files that were written by this install (manifest-tracked).
|
||||
// Scanning the entire targetDir can match user-authored content that
|
||||
@@ -9742,9 +9798,14 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
||||
// #338: local Claude installs write to settings.local.json (Claude Code's per-user/gitignored slot)
|
||||
// so engineer-specific absolute paths (Node binary, home dir) never land in the repo-shared
|
||||
// settings.json. Global installs and all other runtimes continue to use settings.json.
|
||||
const isLocalClaude = (runtime === 'claude' && !isGlobal);
|
||||
const settingsFileName = isLocalClaude ? 'settings.local.json' : 'settings.json';
|
||||
const settingsPath = path.join(targetDir, settingsFileName);
|
||||
const _scopedSettings = _hostBehaviors(runtime).settingsFileByScope || null;
|
||||
const isLocalClaude = (!isGlobal && !!(_scopedSettings && _scopedSettings.local));
|
||||
const settingsFileName = isLocalClaude
|
||||
? _scopedSettings.local
|
||||
: ((_scopedSettings && _scopedSettings.global) || 'settings.json');
|
||||
// ADR-1239 Phase B write-confinement: the descriptor-sourced settings filename
|
||||
// must resolve under targetDir (this path also drives a recursive mkdirSync).
|
||||
const settingsPath = assertDestWithinConfigHome(targetDir, settingsFileName);
|
||||
|
||||
// #338 migration: if a prior local Claude install wrote GSD-shaped entries to settings.json,
|
||||
// relocate them to settings.local.json and clear them from the shared file in the same run.
|
||||
@@ -10010,7 +10071,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
||||
/**
|
||||
* Apply statusline config, then print completion message
|
||||
*/
|
||||
function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline, runtime = 'claude', isGlobal = true, configDir = null, bannerOpts = {}) {
|
||||
function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline, runtime = DEFAULT_RUNTIME, isGlobal = true, configDir = null, bannerOpts = {}) {
|
||||
const { isOpencode, isKilo, isCodex, isCopilot, isAntigravity, isCursor, isWindsurf, isAugment, isTrae, isQwen, isHermes, isCodebuddy, isCline, isKimi } = runtimeFlags(runtime);
|
||||
const plan = resolveInstallPlan(runtime);
|
||||
|
||||
@@ -10069,7 +10130,7 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
||||
// Merges GSD-owned entries non-destructively (preserves existing user permissions).
|
||||
// Scoped to Claude only: antigravity/qwen/hermes/codebuddy also write
|
||||
// settings.json but use different runtimes and do not use these permission strings.
|
||||
if (runtime === 'claude') {
|
||||
if (_hostBehaviors(runtime).permissionsSchema === 'claude') {
|
||||
mergeClaudePermissions(settings);
|
||||
}
|
||||
|
||||
@@ -10102,7 +10163,7 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
||||
// chat model instead of pinning the resolved model. See #1156 (default-to-omit
|
||||
// intent) and #1569 (preserve explicit true). Guard matches the #130-class pattern
|
||||
// on configureOpencodePermissions above.
|
||||
if (runtime !== 'claude' && !process.env.GSD_TEST_MODE) {
|
||||
if (!_hostBehaviors(runtime).nativeModelAliases && !process.env.GSD_TEST_MODE) {
|
||||
const gsdDir = path.join(os.homedir(), '.gsd');
|
||||
const defaultsPath = path.join(gsdDir, 'defaults.json');
|
||||
try {
|
||||
@@ -10144,7 +10205,7 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
||||
// Restart is required for CC to pick up newly-installed skills, and the
|
||||
// slash-menu surface depends on CC version — so the instruction needs to
|
||||
// cover both invocation paths to avoid #2957-style "no commands appear".
|
||||
if (runtime === 'claude' && isGlobal) {
|
||||
if (_hostBehaviors(runtime).skillsGlobalOnboarding && isGlobal) {
|
||||
console.log(`
|
||||
${green}Done!${reset} Restart ${program}, then in any directory either type ${cyan}${command}${reset} or ask Claude to run the ${cyan}gsd-new-project${reset} skill.
|
||||
|
||||
@@ -10303,7 +10364,7 @@ function parseRuntimeInput(answer) {
|
||||
}
|
||||
}
|
||||
|
||||
return selected.length > 0 ? selected : ['claude'];
|
||||
return selected.length > 0 ? selected : [DEFAULT_RUNTIME];
|
||||
}
|
||||
|
||||
function promptRuntime(callback) {
|
||||
@@ -10922,7 +10983,7 @@ function installAllRuntimes(runtimes, isGlobal, isInteractive) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const statuslineRuntimes = ['claude'];
|
||||
const statuslineRuntimes = [DEFAULT_RUNTIME];
|
||||
const primaryStatuslineResult = results.find(r => statuslineRuntimes.includes(r.runtime));
|
||||
|
||||
const finalize = (shouldInstallStatusline, shouldInstallBanner) => {
|
||||
@@ -11206,7 +11267,7 @@ if (require.main === module && !process.env.GSD_TEST_MODE) {
|
||||
console.error(` ${yellow}--uninstall requires --global or --local${reset}`);
|
||||
process.exit(1);
|
||||
}
|
||||
const runtimes = selectedRuntimes.length > 0 ? selectedRuntimes : ['claude'];
|
||||
const runtimes = selectedRuntimes.length > 0 ? selectedRuntimes : [DEFAULT_RUNTIME];
|
||||
for (const runtime of runtimes) {
|
||||
uninstall(hasGlobal, runtime);
|
||||
}
|
||||
@@ -11218,12 +11279,12 @@ if (require.main === module && !process.env.GSD_TEST_MODE) {
|
||||
}
|
||||
} else if (hasGlobal || hasLocal) {
|
||||
// Default to Claude if no runtime specified but location is
|
||||
installAllRuntimes(['claude'], hasGlobal, false);
|
||||
installAllRuntimes([DEFAULT_RUNTIME], hasGlobal, false);
|
||||
} else {
|
||||
// Interactive
|
||||
if (!process.stdin.isTTY) {
|
||||
console.log(` ${yellow}Non-interactive terminal detected, defaulting to Claude Code global install${reset}\n`);
|
||||
installAllRuntimes(['claude'], true, false);
|
||||
installAllRuntimes([DEFAULT_RUNTIME], true, false);
|
||||
} else {
|
||||
promptRuntime((runtimes) => {
|
||||
promptLocation(runtimes);
|
||||
|
||||
@@ -79,6 +79,21 @@
|
||||
"stateIO": "filesystem",
|
||||
"transport": "mcp",
|
||||
"runtime": "node"
|
||||
},
|
||||
"hostBehaviors": {
|
||||
"attributionSource": "settings-json-commit",
|
||||
"authorsCanonicalWorkflow": true,
|
||||
"localInstallStyle": "legacy-flat",
|
||||
"permissionsSchema": "claude",
|
||||
"settingsFileByScope": {
|
||||
"local": "settings.local.json",
|
||||
"global": "settings.json"
|
||||
},
|
||||
"sourceMarkerFile": ".gsd-source",
|
||||
"agentFrontmatterExtensions": ["effort"],
|
||||
"ownsClaudePaths": true,
|
||||
"nativeModelAliases": true,
|
||||
"skillsGlobalOnboarding": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +102,51 @@ a first-party primitive, reviewed. To add one (e.g. a new `runtime` kind):
|
||||
The parity guard (`tests/host-integration-validator-parity.test.cjs`) fails if these two drift, so
|
||||
they must be updated together. Document the new value's meaning in the matrix legend.
|
||||
|
||||
## 7. Fold an already-hardcoded host into the interface (worked example: `claude`)
|
||||
|
||||
Sections 1–6 cover a *green-field* host (`pi`, `antigravity` — a fresh descriptor + reference
|
||||
binding). This section covers the other case: a host that already has a **real production install**
|
||||
driven by scattered `runtime === '<id>'` string-equality branches in `bin/install.js`, which you want
|
||||
to move onto the Host-Integration Interface **without changing a single installed byte**. `claude`
|
||||
(the tier-1 reference host, #2086) is the worked example.
|
||||
|
||||
The pattern is byte-parity-safe by construction — each string check becomes a **descriptor lookup that
|
||||
yields the same truth value**, so behavior is unchanged and only the brittle coupling is removed:
|
||||
|
||||
1. **Inventory the branches.** Find every `runtime === '<id>'` / `runtime !== '<id>'` in `bin/install.js`
|
||||
for the host (`grep -nE "runtime\s*[!=]==\s*'claude'"`). Each is a host behavior encoded as a string
|
||||
comparison rather than a declared capability.
|
||||
|
||||
2. **Declare the behaviors on the descriptor.** Add a `runtime.hostBehaviors` object to the host's
|
||||
`capability.json`. Each key names one behavior the branches gated on — e.g. for `claude`:
|
||||
`permissionsSchema: "claude"`, `settingsFileByScope: { local: "settings.local.json", global: "settings.json" }`,
|
||||
`sourceMarkerFile: ".gsd-source"`, `agentFrontmatterExtensions: ["effort"]`, `localInstallStyle: "legacy-flat"`,
|
||||
`authorsCanonicalWorkflow: true`, `ownsClaudePaths: true`, `nativeModelAliases: true`,
|
||||
`skillsGlobalOnboarding: true`, `attributionSource: "settings-json-commit"`. The validator
|
||||
(`validateRuntimeBody`) is lenient toward these host-behavior keys; they carry install policy, not the
|
||||
closed negotiated axes.
|
||||
|
||||
3. **Replace each branch with a descriptor read.** `bin/install.js` exposes a `_hostBehaviors(runtime)`
|
||||
helper (reads `_capabilityRegistry.runtimes[runtime].runtime.hostBehaviors`, `{}` if absent). Rewrite
|
||||
`if (runtime === 'claude')` → `if (_hostBehaviors(runtime).permissionsSchema === 'claude')`, and
|
||||
`if (runtime !== 'claude')` → `if (!_hostBehaviors(runtime).authorsCanonicalWorkflow)`. Only the host
|
||||
declares the key, so every other runtime keeps the generic path.
|
||||
|
||||
4. **Route install/uninstall through the public adapter.** Replace the direct
|
||||
`installRuntimeArtifacts(...)` / `uninstallRuntimeArtifacts(...)` calls with
|
||||
`createImperativeAdapter({ runtime }).install({...})` / `.uninstall({...})`. The imperative adapter
|
||||
delegates to the *same* engine functions, so the output is byte-identical — that is the point: the
|
||||
host is now driven **through** the interface, not around it.
|
||||
|
||||
5. **Prove parity, both scopes.** `tests/golden-install-parity.test.cjs` captures a byte-stable manifest
|
||||
of every emitted file. Assert the host's install is unchanged for **global and local** scopes
|
||||
(regenerate a baseline from `origin/next` first, then confirm the migrated tree matches it). Exclude
|
||||
only genuinely volatile / platform-varying files (`settings.json`, `settings.local.json`, `.gsd-source`).
|
||||
|
||||
6. **Guard against regression.** Add a `*-imperative-reference.test.cjs` asserting the adapter classifies
|
||||
the host correctly, negotiation fails closed on a corrupted descriptor, and — with a source-grep behind
|
||||
an `// allow-test-rule:` exemption — that **no `runtime === '<id>'` branch remains** in `bin/install.js`.
|
||||
|
||||
---
|
||||
|
||||
## Related
|
||||
|
||||
@@ -453,6 +453,23 @@ const capabilities = {
|
||||
"stateIO": "filesystem",
|
||||
"transport": "mcp",
|
||||
"runtime": "node"
|
||||
},
|
||||
"hostBehaviors": {
|
||||
"attributionSource": "settings-json-commit",
|
||||
"authorsCanonicalWorkflow": true,
|
||||
"localInstallStyle": "legacy-flat",
|
||||
"permissionsSchema": "claude",
|
||||
"settingsFileByScope": {
|
||||
"local": "settings.local.json",
|
||||
"global": "settings.json"
|
||||
},
|
||||
"sourceMarkerFile": ".gsd-source",
|
||||
"agentFrontmatterExtensions": [
|
||||
"effort"
|
||||
],
|
||||
"ownsClaudePaths": true,
|
||||
"nativeModelAliases": true,
|
||||
"skillsGlobalOnboarding": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3877,6 +3894,23 @@ const runtimes = {
|
||||
"stateIO": "filesystem",
|
||||
"transport": "mcp",
|
||||
"runtime": "node"
|
||||
},
|
||||
"hostBehaviors": {
|
||||
"attributionSource": "settings-json-commit",
|
||||
"authorsCanonicalWorkflow": true,
|
||||
"localInstallStyle": "legacy-flat",
|
||||
"permissionsSchema": "claude",
|
||||
"settingsFileByScope": {
|
||||
"local": "settings.local.json",
|
||||
"global": "settings.json"
|
||||
},
|
||||
"sourceMarkerFile": ".gsd-source",
|
||||
"agentFrontmatterExtensions": [
|
||||
"effort"
|
||||
],
|
||||
"ownsClaudePaths": true,
|
||||
"nativeModelAliases": true,
|
||||
"skillsGlobalOnboarding": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
138
tests/claude-imperative-reference.test.cjs
Normal file
138
tests/claude-imperative-reference.test.cjs
Normal file
@@ -0,0 +1,138 @@
|
||||
// allow-test-rule: AC2 requires asserting no `runtime === 'claude'` string-equality branch remains in bin/install.js — the descriptor-migration contract is a property of the source text, so a source-grep is the only faithful check (#2086)
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* claude imperative reference host — ADR-1239 Phase D / #2086 (EoS/claude).
|
||||
*
|
||||
* claude is GSD's tier-1 reference host — "golden parity vs. the Claude reference
|
||||
* host" (ADR-1239 line 146). This proves claude is driven through the PUBLIC
|
||||
* Host-Integration Interface (the imperative adapter), that its negotiated axes
|
||||
* classify + negotiate correctly, that negotiation FAILS CLOSED on a corrupted
|
||||
* descriptor, and that the migration retired the hardcoded `runtime === 'claude'`
|
||||
* string-equality branches in bin/install.js (folded into descriptor-driven
|
||||
* `runtime.hostBehaviors`).
|
||||
*
|
||||
* Mirrors tests/pi-imperative-reference.test.cjs + tests/vscode-ide-reference.test.cjs
|
||||
* but binds against the REAL descriptor + the REAL installer source, since claude
|
||||
* (unlike pi/vscode) has a real production install being folded in.
|
||||
*/
|
||||
|
||||
const { test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const { createImperativeAdapter } = require('../gsd-core/bin/lib/adapter-imperative.cjs');
|
||||
const {
|
||||
profileOf,
|
||||
negotiateHostCapabilities,
|
||||
PROTOCOL_VERSION,
|
||||
PROFILE_BASELINES,
|
||||
UNDOCUMENTED,
|
||||
} = require('../gsd-core/bin/lib/host-integration.cjs');
|
||||
|
||||
const CLAUDE_CAP = JSON.parse(
|
||||
fs.readFileSync(path.join(__dirname, '..', 'capabilities', 'claude', 'capability.json'), 'utf8'),
|
||||
);
|
||||
const CLAUDE_AXES = CLAUDE_CAP.runtime.hostIntegration;
|
||||
|
||||
// -- AC2: driven through the public interface (imperative adapter) -----------
|
||||
|
||||
test('createImperativeAdapter classifies claude as imperative + composes the registry', () => {
|
||||
const adapter = createImperativeAdapter({ runtime: 'claude' });
|
||||
assert.equal(adapter.kind, 'imperative', "claude embeddingMode is imperative -> adapter kind must be 'imperative'");
|
||||
assert.equal(adapter.runtime, 'claude');
|
||||
assert.ok(adapter.registry && typeof adapter.registry === 'object', 'imperative adapter exposes the composed capability registry');
|
||||
assert.equal(typeof adapter.install, 'function');
|
||||
assert.equal(typeof adapter.uninstall, 'function');
|
||||
});
|
||||
|
||||
test('claude descriptor embeddingMode agrees with the imperative adapter kind', () => {
|
||||
assert.equal(CLAUDE_AXES.embeddingMode, 'imperative', 'capability.json must declare embeddingMode: imperative for the imperative binding');
|
||||
});
|
||||
|
||||
test('claude axes classify as the programmatic-cli reference profile', () => {
|
||||
assert.equal(profileOf(CLAUDE_AXES), 'programmatic-cli');
|
||||
assert.notEqual(profileOf(CLAUDE_AXES), 'ide');
|
||||
});
|
||||
|
||||
// -- AC3: every negotiated axis populated, negotiation is clean ---------------
|
||||
|
||||
test('claude negotiates its declared axes verbatim (no degradation of documented values)', () => {
|
||||
const result = negotiateHostCapabilities({ ...CLAUDE_AXES, protocolVersion: PROTOCOL_VERSION });
|
||||
assert.equal(result.protocolVersion, PROTOCOL_VERSION);
|
||||
// Every declared scalar axis survives negotiation unchanged (all are known+documented).
|
||||
assert.equal(result.effective.embeddingMode, 'imperative');
|
||||
assert.equal(result.effective.commandSurface, CLAUDE_AXES.commandSurface);
|
||||
assert.equal(result.effective.modelMode, CLAUDE_AXES.modelMode);
|
||||
assert.equal(result.effective.hookBus, CLAUDE_AXES.hookBus);
|
||||
assert.equal(result.effective.stateIO, CLAUDE_AXES.stateIO);
|
||||
assert.equal(result.effective.transport, CLAUDE_AXES.transport);
|
||||
assert.equal(result.effective.runtime, CLAUDE_AXES.runtime);
|
||||
// No `undocumented` sentinel anywhere in claude's declared axes.
|
||||
assert.ok(
|
||||
!JSON.stringify(CLAUDE_AXES).includes(UNDOCUMENTED),
|
||||
'claude descriptor must carry no `undocumented` sentinel (fully doc-sourced)',
|
||||
);
|
||||
});
|
||||
|
||||
// -- AC5: negotiation fails CLOSED on a corrupted / partial descriptor --------
|
||||
|
||||
test('negotiateHostCapabilities never throws for claude — even fully corrupted input', () => {
|
||||
assert.doesNotThrow(() => negotiateHostCapabilities({}));
|
||||
assert.doesNotThrow(() => negotiateHostCapabilities({ embeddingMode: UNDOCUMENTED }));
|
||||
assert.doesNotThrow(() => negotiateHostCapabilities({ embeddingMode: 'wildly-unknown-future-value' }));
|
||||
});
|
||||
|
||||
test('a partial/empty claude descriptor degrades to the safe floor — NOT the full programmatic-cli baseline', () => {
|
||||
const result = negotiateHostCapabilities({});
|
||||
// Fail-closed floor (SAFE_DEFAULTS), not the rich profile baseline.
|
||||
assert.equal(result.effective.embeddingMode, 'declarative', 'omitted embeddingMode degrades closed to declarative');
|
||||
assert.equal(result.effective.hookBus, 'none', 'omitted hookBus degrades closed to none');
|
||||
assert.equal(result.effective.commandSurface, 'prose-only', 'omitted commandSurface degrades closed to prose-only');
|
||||
assert.equal(result.effective.dispatch.namedDispatch, false, 'omitted dispatch degrades closed (no named dispatch)');
|
||||
assert.notDeepEqual(
|
||||
result.effective,
|
||||
PROFILE_BASELINES['programmatic-cli'],
|
||||
'a corrupted descriptor MUST NOT silently reuse the full programmatic-cli baseline',
|
||||
);
|
||||
assert.ok(result.warnings.length > 0, 'degrade-closed must surface warnings');
|
||||
});
|
||||
|
||||
test('an undocumented/unknown claude axis value is not trusted (degraded closed per-axis)', () => {
|
||||
const corrupted = { ...CLAUDE_AXES, embeddingMode: UNDOCUMENTED, hookBus: 'unknown-bus-kind' };
|
||||
const result = negotiateHostCapabilities(corrupted);
|
||||
assert.equal(result.effective.embeddingMode, 'declarative', 'undocumented embeddingMode -> safe floor');
|
||||
assert.equal(result.effective.hookBus, 'none', 'unknown hookBus value -> safe floor');
|
||||
// Untouched axes still negotiate to their declared (documented) values.
|
||||
assert.equal(result.effective.stateIO, CLAUDE_AXES.stateIO);
|
||||
});
|
||||
|
||||
// -- AC2: the hardcoded string-equality branches are retired ------------------
|
||||
|
||||
test('claude descriptor declares runtime.hostBehaviors (the folded-in host behaviors)', () => {
|
||||
const hb = CLAUDE_CAP.runtime.hostBehaviors;
|
||||
assert.ok(hb && typeof hb === 'object', 'capabilities/claude/capability.json must declare runtime.hostBehaviors');
|
||||
// The behaviors that replaced the 13 `runtime === 'claude'` branches.
|
||||
assert.equal(hb.permissionsSchema, 'claude');
|
||||
assert.equal(hb.localInstallStyle, 'legacy-flat');
|
||||
assert.equal(hb.sourceMarkerFile, '.gsd-source');
|
||||
assert.equal(hb.authorsCanonicalWorkflow, true);
|
||||
assert.equal(hb.ownsClaudePaths, true);
|
||||
assert.equal(hb.nativeModelAliases, true);
|
||||
assert.equal(hb.skillsGlobalOnboarding, true);
|
||||
assert.equal(hb.attributionSource, 'settings-json-commit');
|
||||
assert.deepEqual(hb.agentFrontmatterExtensions, ['effort']);
|
||||
assert.equal(hb.settingsFileByScope.local, 'settings.local.json');
|
||||
assert.equal(hb.settingsFileByScope.global, 'settings.json');
|
||||
});
|
||||
|
||||
test('bin/install.js contains no `runtime === "claude"` / `runtime !== "claude"` string-equality branches (AC2)', () => {
|
||||
const src = fs.readFileSync(path.join(__dirname, '..', 'bin', 'install.js'), 'utf8');
|
||||
const offenders = src.match(/runtime\s*[!=]==\s*'claude'/g) || [];
|
||||
assert.deepEqual(
|
||||
offenders,
|
||||
[],
|
||||
`AC2: every hardcoded runtime==='claude'/!=='claude' branch must be descriptor-driven; found: ${offenders.join(', ')}`,
|
||||
);
|
||||
});
|
||||
418
tests/fixtures/golden-install-parity/claude-local.json
vendored
Normal file
418
tests/fixtures/golden-install-parity/claude-local.json
vendored
Normal file
@@ -0,0 +1,418 @@
|
||||
{
|
||||
".gsd-profile": "0e716a5fef4e6dc1",
|
||||
"agents/gsd-advisor-researcher.md": "4e1a8afe45ef8298",
|
||||
"agents/gsd-ai-researcher.md": "1de9bb9c75e3d668",
|
||||
"agents/gsd-assumptions-analyzer.md": "63c8bdab78b42128",
|
||||
"agents/gsd-code-fixer.md": "341e9d4e5c081f0e",
|
||||
"agents/gsd-code-reviewer.md": "65009b1743904fd3",
|
||||
"agents/gsd-codebase-mapper.md": "6b58924e7ab595b6",
|
||||
"agents/gsd-debug-session-manager.md": "df63cabbe4d062a0",
|
||||
"agents/gsd-debugger.md": "f6874f2e4e74c6fe",
|
||||
"agents/gsd-doc-classifier.md": "4f52b062005868ec",
|
||||
"agents/gsd-doc-synthesizer.md": "4be3aac2095d021d",
|
||||
"agents/gsd-doc-verifier.md": "4232dcf9076e3566",
|
||||
"agents/gsd-doc-writer.md": "a8af8a58c28e06b2",
|
||||
"agents/gsd-domain-researcher.md": "c5f1f069f3124844",
|
||||
"agents/gsd-eval-auditor.md": "d8debe9e60c26574",
|
||||
"agents/gsd-eval-planner.md": "8b2359a18a618b04",
|
||||
"agents/gsd-executor.md": "abf1192f0ac098dc",
|
||||
"agents/gsd-framework-selector.md": "a359da24df68f01c",
|
||||
"agents/gsd-integration-checker.md": "1d2ca1591310bf70",
|
||||
"agents/gsd-intel-updater.md": "ae95a5f8b73a465b",
|
||||
"agents/gsd-mempalace-curator.md": "77b53f1b155242b4",
|
||||
"agents/gsd-nyquist-auditor.md": "06e42106449d17ac",
|
||||
"agents/gsd-pattern-mapper.md": "b45b5e106775bec1",
|
||||
"agents/gsd-phase-researcher.md": "90f97c741075c907",
|
||||
"agents/gsd-plan-checker.md": "63e7988a3d7db5cf",
|
||||
"agents/gsd-planner.md": "0fb1385f2ea179f0",
|
||||
"agents/gsd-project-researcher.md": "78d92594b3de6e51",
|
||||
"agents/gsd-research-synthesizer.md": "635639f831464f95",
|
||||
"agents/gsd-roadmapper.md": "90d11d44ac7d5121",
|
||||
"agents/gsd-security-auditor.md": "c8eead1524c5a465",
|
||||
"agents/gsd-ui-auditor.md": "94868774ac120a38",
|
||||
"agents/gsd-ui-checker.md": "505ca13a25cb67df",
|
||||
"agents/gsd-ui-researcher.md": "4596a60f979dc769",
|
||||
"agents/gsd-user-profiler.md": "a5cd095688d6de25",
|
||||
"agents/gsd-verifier.md": "7327f257ade6d00f",
|
||||
"commands/gsd-add-tests.md": "993d9c16477b17bf",
|
||||
"commands/gsd-ai-integration-phase.md": "bd8407401cc52092",
|
||||
"commands/gsd-audit-fix.md": "0ce7dff55a550d96",
|
||||
"commands/gsd-audit-milestone.md": "506d3d20f55d80ce",
|
||||
"commands/gsd-audit-uat.md": "4aefa85d28437405",
|
||||
"commands/gsd-autonomous.md": "ef5a492c9085b983",
|
||||
"commands/gsd-capture.md": "61647c8baefcb116",
|
||||
"commands/gsd-cleanup.md": "88e8c3d4b471a036",
|
||||
"commands/gsd-code-review.md": "98a9c59ff3edb4ab",
|
||||
"commands/gsd-complete-milestone.md": "7639dce1b9e755e4",
|
||||
"commands/gsd-config.md": "b36340912b70c073",
|
||||
"commands/gsd-debug.md": "e622240e54587e1b",
|
||||
"commands/gsd-discuss-phase.md": "9eebccc3820ad37a",
|
||||
"commands/gsd-docs-update.md": "a11bedfbfd0a56db",
|
||||
"commands/gsd-eval-review.md": "dc602abbb7525761",
|
||||
"commands/gsd-execute-phase.md": "f47b83e2110f7be8",
|
||||
"commands/gsd-explore.md": "dbef734b08f7438a",
|
||||
"commands/gsd-extract-learnings.md": "f3059f4e6e6b7b19",
|
||||
"commands/gsd-fast.md": "be784c9f67ae3eab",
|
||||
"commands/gsd-forensics.md": "36ec35a6a6f2cfd7",
|
||||
"commands/gsd-graphify.md": "1b88285a888f69dd",
|
||||
"commands/gsd-health.md": "f28dabf45c4ddd77",
|
||||
"commands/gsd-help.md": "e48f4e1cf9ff3ad3",
|
||||
"commands/gsd-import.md": "849c4a4bdfc05ebf",
|
||||
"commands/gsd-inbox.md": "61eebdd7397efe7f",
|
||||
"commands/gsd-ingest-docs.md": "10b1c8fee7ed7c1f",
|
||||
"commands/gsd-manager.md": "0c5541f3db284fe3",
|
||||
"commands/gsd-map-codebase.md": "9c32f13ade27d418",
|
||||
"commands/gsd-mempalace-capture.md": "2e49397072506fd9",
|
||||
"commands/gsd-mempalace-recall.md": "38716c0983a3ef9c",
|
||||
"commands/gsd-milestone-summary.md": "a7fdafaaa3a665af",
|
||||
"commands/gsd-mvp-phase.md": "fe9bf9df99007be1",
|
||||
"commands/gsd-new-milestone.md": "d2f21e6e3d53b2e9",
|
||||
"commands/gsd-new-project.md": "2129c259fa653b19",
|
||||
"commands/gsd-next.md": "02d0abbd2f55a0a2",
|
||||
"commands/gsd-ns-context.md": "011c44e7aa46e64a",
|
||||
"commands/gsd-ns-ideate.md": "edc5e543512dd48a",
|
||||
"commands/gsd-ns-manage.md": "0409d810e499357f",
|
||||
"commands/gsd-ns-project.md": "ff67e85bc6f7fc5a",
|
||||
"commands/gsd-ns-review.md": "3766ed10827882a0",
|
||||
"commands/gsd-ns-workflow.md": "c3b3c046a74ec0ee",
|
||||
"commands/gsd-onboard.md": "2d98ea48454293b5",
|
||||
"commands/gsd-pause-work.md": "bdfc8f46a674061a",
|
||||
"commands/gsd-phase.md": "b211ba5969b4d69b",
|
||||
"commands/gsd-plan-phase.md": "8113654accd5bee9",
|
||||
"commands/gsd-plan-review-convergence.md": "3d496dbbba45190c",
|
||||
"commands/gsd-pr-branch.md": "2346583e447a46d6",
|
||||
"commands/gsd-profile-user.md": "8cc1a47fe685b60e",
|
||||
"commands/gsd-progress.md": "0eddf50b039adc8e",
|
||||
"commands/gsd-quick.md": "116941d841ecfb07",
|
||||
"commands/gsd-resume-work.md": "330cd86844bd768b",
|
||||
"commands/gsd-review-backlog.md": "434ebe7b63c107e0",
|
||||
"commands/gsd-review.md": "65b5225d8405f290",
|
||||
"commands/gsd-secure-phase.md": "1ab3de7d982a7b9a",
|
||||
"commands/gsd-settings.md": "13a4293f7fa58276",
|
||||
"commands/gsd-ship.md": "551dc7f66e14a3de",
|
||||
"commands/gsd-sketch.md": "0d01a055aa4b00ee",
|
||||
"commands/gsd-spec-phase.md": "b3f6c26deba9172f",
|
||||
"commands/gsd-spike.md": "7c102d63239f6678",
|
||||
"commands/gsd-stats.md": "dc7c484c0fc2c177",
|
||||
"commands/gsd-surface.md": "81c0c0d634ae0375",
|
||||
"commands/gsd-thread.md": "4d9ac2986e090f5f",
|
||||
"commands/gsd-ui-phase.md": "19ab8467a3f737a6",
|
||||
"commands/gsd-ui-review.md": "5e0e6a377719a125",
|
||||
"commands/gsd-ultraplan-phase.md": "29426afa8f6bcef3",
|
||||
"commands/gsd-undo.md": "3a8299018ada31f6",
|
||||
"commands/gsd-update.md": "22f2060ab482d569",
|
||||
"commands/gsd-validate-phase.md": "079a0013bd14c1bd",
|
||||
"commands/gsd-verify-work.md": "a7b07cc4547e7099",
|
||||
"commands/gsd-workspace.md": "24b4c632d9773c11",
|
||||
"commands/gsd-workstreams.md": "52ab9c585d3a00f3",
|
||||
"gsd-core/VERSION": "ef0deccd81a6723c",
|
||||
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
|
||||
"gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62",
|
||||
"gsd-core/bin/gsd-tools.cjs": "6a7616125440c8b1",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
||||
"gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
||||
"gsd-core/contexts/research.md": "b3285d8e7209cc3b",
|
||||
"gsd-core/contexts/review.md": "dc578fdd74bbea11",
|
||||
"gsd-core/references/agent-contracts.md": "ff65e633c656c0d2",
|
||||
"gsd-core/references/agent-skills-bootstrap.md": "5ab875054b1adda9",
|
||||
"gsd-core/references/ai-evals.md": "b5afa786b938671e",
|
||||
"gsd-core/references/ai-frameworks.md": "f827de93dde124eb",
|
||||
"gsd-core/references/api-coverage.md": "205a43c5fa7c221c",
|
||||
"gsd-core/references/artifact-types.md": "08e3c3aa1f2ac11e",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
|
||||
"gsd-core/references/checkpoints.md": "6aa620c6ca38bdf0",
|
||||
"gsd-core/references/common-bug-patterns.md": "780145be56352626",
|
||||
"gsd-core/references/context-budget.md": "338acaf52853668c",
|
||||
"gsd-core/references/continuation-format.md": "580287399ad3ba68",
|
||||
"gsd-core/references/debugger-philosophy.md": "0466f95a3d6bfcd3",
|
||||
"gsd-core/references/decimal-phase-calculation.md": "46b5ba045852c474",
|
||||
"gsd-core/references/doc-conflict-engine.md": "883d0a1b9d9ff96e",
|
||||
"gsd-core/references/domain-probes.md": "762b965e84035b72",
|
||||
"gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json": "72d1e29cedc854ec",
|
||||
"gsd-core/references/edge-probe-fixtures/01-round-half-even/requirements.json": "fbc1b355d8625eeb",
|
||||
"gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json": "fad67dcc8294f6da",
|
||||
"gsd-core/references/edge-probe-fixtures/02-merge-intervals/requirements.json": "30a78ee9ce3473ea",
|
||||
"gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json": "66dd60957fee45f0",
|
||||
"gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/requirements.json": "47fca61f076835fa",
|
||||
"gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json": "72d1e29cedc854ec",
|
||||
"gsd-core/references/edge-probe-fixtures/04-money-rounding/requirements.json": "80f04f5c04fb24cf",
|
||||
"gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json": "fad67dcc8294f6da",
|
||||
"gsd-core/references/edge-probe-fixtures/05-list-dedupe/requirements.json": "d38147adb0e5b342",
|
||||
"gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json": "bc552c01939bf4f8",
|
||||
"gsd-core/references/edge-probe-fixtures/06-resolved-mixed/requirements.json": "30a78ee9ce3473ea",
|
||||
"gsd-core/references/edge-probe-fixtures/06-resolved-mixed/resolutions.json": "688ec62c13e08afe",
|
||||
"gsd-core/references/edge-probe.md": "92e6574b30f5b765",
|
||||
"gsd-core/references/execute-mvp-tdd.md": "a98a270a7ab126bc",
|
||||
"gsd-core/references/execute-phase-between-wave-reset.md": "3ad96ca0f7fee37e",
|
||||
"gsd-core/references/execute-phase-context-guard.md": "a5a1058d35806a8e",
|
||||
"gsd-core/references/execute-phase-wave-guard.md": "de9ac22cead4cfd8",
|
||||
"gsd-core/references/executor-examples.md": "ba59243ed45c8ab1",
|
||||
"gsd-core/references/few-shot-examples/plan-checker.md": "2574808188ac9de4",
|
||||
"gsd-core/references/few-shot-examples/verifier.md": "5badee4560b14ae8",
|
||||
"gsd-core/references/gate-prompts.md": "099c8d52e3562336",
|
||||
"gsd-core/references/gates.md": "7dc9fd3a3d6217c6",
|
||||
"gsd-core/references/git-integration.md": "5c70ef3203b7c9ce",
|
||||
"gsd-core/references/git-planning-commit.md": "f897a15ebfc3f5a7",
|
||||
"gsd-core/references/gsd-run-resolver.md": "46173caac1d469d4",
|
||||
"gsd-core/references/honest-verifier.md": "6e4b8293f644b3f1",
|
||||
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
|
||||
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
|
||||
"gsd-core/references/mandatory-initial-read.md": "fe59abce693717cf",
|
||||
"gsd-core/references/model-profile-resolution.md": "a2fa775cac3470fa",
|
||||
"gsd-core/references/model-profiles.md": "c249163663bbea53",
|
||||
"gsd-core/references/mvp-concepts.md": "3464783eaaef5c10",
|
||||
"gsd-core/references/phase-argument-parsing.md": "e5bbb985f3bc3e34",
|
||||
"gsd-core/references/planner-antipatterns.md": "7ed54ec1e2cc54ac",
|
||||
"gsd-core/references/planner-chunked.md": "79fe674221e738e6",
|
||||
"gsd-core/references/planner-gap-closure.md": "76bee257911413e7",
|
||||
"gsd-core/references/planner-graphify-auto-update.md": "1ed614dfba72f2a3",
|
||||
"gsd-core/references/planner-guidance.md": "563d1a434304d5cc",
|
||||
"gsd-core/references/planner-human-verify-mode.md": "56d05e841630b3f4",
|
||||
"gsd-core/references/planner-interface-context.md": "b28fa3da6ae739a8",
|
||||
"gsd-core/references/planner-load-graph-context.md": "0126c6156a9d32a8",
|
||||
"gsd-core/references/planner-mvp-mode.md": "f7fc3894770a73f8",
|
||||
"gsd-core/references/planner-reviews.md": "da39eace09a10743",
|
||||
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
|
||||
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
|
||||
"gsd-core/references/planning-config.md": "b025429fc72f9285",
|
||||
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
|
||||
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
|
||||
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
|
||||
"gsd-core/references/prohibition-probe.md": "7612bb55f00359bb",
|
||||
"gsd-core/references/project-skills-discovery.md": "c155e03dce8dc3c2",
|
||||
"gsd-core/references/questioning.md": "a8c988cab05f4651",
|
||||
"gsd-core/references/research-documentation-lookup.md": "c070007d1d72ab71",
|
||||
"gsd-core/references/research-philosophy.md": "62930e66cc979c1a",
|
||||
"gsd-core/references/research-verification-protocol.md": "9c38c9d9a687e679",
|
||||
"gsd-core/references/reviewer-instances.md": "1412472f858b8f41",
|
||||
"gsd-core/references/revision-loop.md": "e55ff32dd98c63df",
|
||||
"gsd-core/references/scout-codebase.md": "ba266ecc18fbf172",
|
||||
"gsd-core/references/security-asvs-levels.md": "4774fac3b94b6ca8",
|
||||
"gsd-core/references/skeleton-template.md": "528691d1f0efa878",
|
||||
"gsd-core/references/sketch-interactivity.md": "7d982fe877e1e1cc",
|
||||
"gsd-core/references/sketch-theme-system.md": "33e2e96e450456f8",
|
||||
"gsd-core/references/sketch-tooling.md": "df6c4f24c1c27611",
|
||||
"gsd-core/references/sketch-variant-patterns.md": "66c197aa4fb52810",
|
||||
"gsd-core/references/specless-probe-fallback.md": "d22e89615c846b72",
|
||||
"gsd-core/references/spidr-splitting.md": "074ac154c0e4f906",
|
||||
"gsd-core/references/tdd.md": "e4708ede157478b6",
|
||||
"gsd-core/references/thinking-models-debug.md": "2da61022b16c4e7c",
|
||||
"gsd-core/references/thinking-models-execution.md": "dcc650a8b5f3e049",
|
||||
"gsd-core/references/thinking-models-planning.md": "7e19462313fa028f",
|
||||
"gsd-core/references/thinking-models-research.md": "5f6bf3f3b889c6e4",
|
||||
"gsd-core/references/thinking-models-verification.md": "a71a933d51ca3d8d",
|
||||
"gsd-core/references/thinking-partner.md": "827c1badf3e6df41",
|
||||
"gsd-core/references/ui-brand.md": "48717bcfcd63bd27",
|
||||
"gsd-core/references/universal-anti-patterns.md": "6a1245050b21df01",
|
||||
"gsd-core/references/untrusted-input-boundary.md": "d33b80d4d348599a",
|
||||
"gsd-core/references/user-profiling.md": "b50416fe57c1b321",
|
||||
"gsd-core/references/user-story-template.md": "0cc50e06a144ff8a",
|
||||
"gsd-core/references/verification-overrides.md": "a3e2d5166d16a37b",
|
||||
"gsd-core/references/verification-patterns.md": "57c67c5fd375fcb3",
|
||||
"gsd-core/references/verify-mvp-mode.md": "534bdc7f2432903a",
|
||||
"gsd-core/references/workstream-flag.md": "ca99ca79e716f0f5",
|
||||
"gsd-core/references/worktree-branch-check.md": "21d9c31bf6542b93",
|
||||
"gsd-core/references/worktree-path-safety.md": "3c8d74756f9b16a8",
|
||||
"gsd-core/templates/AI-SPEC.md": "24df5fe5ba34e367",
|
||||
"gsd-core/templates/DEBUG.md": "57bd61bfd1d98e7e",
|
||||
"gsd-core/templates/README.md": "90d2617778373147",
|
||||
"gsd-core/templates/SECURITY.md": "b628f7f1c6d2328f",
|
||||
"gsd-core/templates/UAT.md": "68d32d1fea14e184",
|
||||
"gsd-core/templates/UI-SPEC.md": "20ca56a4e3e21f01",
|
||||
"gsd-core/templates/VALIDATION.md": "6144951011cdca57",
|
||||
"gsd-core/templates/claude-md.md": "d8f0fe8dba3bb28a",
|
||||
"gsd-core/templates/codebase/architecture.md": "6be88214162fdd89",
|
||||
"gsd-core/templates/codebase/concerns.md": "efa26d1fb5132f25",
|
||||
"gsd-core/templates/codebase/conventions.md": "c2e07698dad6b364",
|
||||
"gsd-core/templates/codebase/integrations.md": "39bd23c71eedd564",
|
||||
"gsd-core/templates/codebase/stack.md": "116e7e67dd87ddec",
|
||||
"gsd-core/templates/codebase/structure.md": "b106ec2b588d2b51",
|
||||
"gsd-core/templates/codebase/testing.md": "76abff7f2050c9ea",
|
||||
"gsd-core/templates/config.json": "a4b783ef759a0f37",
|
||||
"gsd-core/templates/context.md": "69b01e7909ea3f66",
|
||||
"gsd-core/templates/continue-here.md": "f522a51b6895fba8",
|
||||
"gsd-core/templates/copilot-instructions.md": "aea34bc52ff548ea",
|
||||
"gsd-core/templates/debug-subagent-prompt.md": "8c18a89e25929d8e",
|
||||
"gsd-core/templates/dev-preferences.md": "95048a71063d980b",
|
||||
"gsd-core/templates/discovery.md": "e4ab738326eb70e0",
|
||||
"gsd-core/templates/discussion-log.md": "cac1b48ec0f4dcb8",
|
||||
"gsd-core/templates/milestone-archive.md": "591b6decdc0c0e51",
|
||||
"gsd-core/templates/milestone.md": "74d2f750ae9f4a9c",
|
||||
"gsd-core/templates/phase-prompt.md": "d07f52e21f15f2ab",
|
||||
"gsd-core/templates/planner-subagent-prompt.md": "6c9f1b23ee3dc05f",
|
||||
"gsd-core/templates/project.md": "ae1f68db042c2522",
|
||||
"gsd-core/templates/requirements.md": "a44de4c2f146e473",
|
||||
"gsd-core/templates/research-project/ARCHITECTURE.md": "746b9ef791d758b0",
|
||||
"gsd-core/templates/research-project/FEATURES.md": "f2b800de5df91b0f",
|
||||
"gsd-core/templates/research-project/PITFALLS.md": "3ef75fa768422eec",
|
||||
"gsd-core/templates/research-project/STACK.md": "82c85799ac4dd344",
|
||||
"gsd-core/templates/research-project/SUMMARY.md": "dceb2f346388839d",
|
||||
"gsd-core/templates/research.md": "fa6dfb2ff2e8d273",
|
||||
"gsd-core/templates/retrospective.md": "03981e30dd760103",
|
||||
"gsd-core/templates/roadmap.md": "e4e35a9eb5dd4d4f",
|
||||
"gsd-core/templates/spec.md": "7dc900c355098d8b",
|
||||
"gsd-core/templates/state.md": "73e424b8c70b765c",
|
||||
"gsd-core/templates/summary-complex.md": "a5e40574fd8894dc",
|
||||
"gsd-core/templates/summary-minimal.md": "7d09b5e709e2e67c",
|
||||
"gsd-core/templates/summary-standard.md": "e8d9cf4a8377cdff",
|
||||
"gsd-core/templates/summary.md": "23c40f6503b3ea98",
|
||||
"gsd-core/templates/user-profile.md": "20749f23e4c413fc",
|
||||
"gsd-core/templates/user-setup.md": "78b7d718b6e8d67c",
|
||||
"gsd-core/templates/verification-report.md": "dd5faa6254183731",
|
||||
"gsd-core/workflows/_runtime-launcher.snippet.sh": "bf2dd5d1debd5335",
|
||||
"gsd-core/workflows/add-backlog.md": "450f70cb4ba4bf78",
|
||||
"gsd-core/workflows/add-phase.md": "500274ecffbc2b12",
|
||||
"gsd-core/workflows/add-tests.md": "575110e32af4eac3",
|
||||
"gsd-core/workflows/add-todo.md": "4f68274dbf475704",
|
||||
"gsd-core/workflows/ai-integration-phase.md": "1be55b37af12a639",
|
||||
"gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c",
|
||||
"gsd-core/workflows/audit-fix.md": "bab7ebaee574e486",
|
||||
"gsd-core/workflows/audit-milestone.md": "3b5667e761f97774",
|
||||
"gsd-core/workflows/audit-uat.md": "470c4637eb53b4a3",
|
||||
"gsd-core/workflows/autonomous.md": "275e66d559d76792",
|
||||
"gsd-core/workflows/check-todos.md": "dd1504bb42b17021",
|
||||
"gsd-core/workflows/cleanup.md": "e06b655e5e901031",
|
||||
"gsd-core/workflows/code-review-fix.md": "c6d26d40e143acca",
|
||||
"gsd-core/workflows/code-review.md": "1bb99932450e9ced",
|
||||
"gsd-core/workflows/complete-milestone.md": "7f0346ab9b6ab2b4",
|
||||
"gsd-core/workflows/debug.md": "722e00ef94fdb5c2",
|
||||
"gsd-core/workflows/diagnose-issues.md": "17fa373be09a0b82",
|
||||
"gsd-core/workflows/discovery-phase.md": "b9f80db0c22d723a",
|
||||
"gsd-core/workflows/discuss-phase-assumptions.md": "e3693f4e1746bc13",
|
||||
"gsd-core/workflows/discuss-phase-power.md": "290c0d83d783f9f6",
|
||||
"gsd-core/workflows/discuss-phase.md": "6f5c0280e386a12e",
|
||||
"gsd-core/workflows/discuss-phase/modes/advisor.md": "93b4ff65a03a5487",
|
||||
"gsd-core/workflows/discuss-phase/modes/all.md": "fa70d79066562e54",
|
||||
"gsd-core/workflows/discuss-phase/modes/analyze.md": "da0788f3be7f8105",
|
||||
"gsd-core/workflows/discuss-phase/modes/auto.md": "fe5daf7c8617e26b",
|
||||
"gsd-core/workflows/discuss-phase/modes/batch.md": "6946597770e2d448",
|
||||
"gsd-core/workflows/discuss-phase/modes/chain.md": "2d1f9f2396f5d0f4",
|
||||
"gsd-core/workflows/discuss-phase/modes/default.md": "67d1b67f61f03966",
|
||||
"gsd-core/workflows/discuss-phase/modes/power.md": "7c10e13c60b73af0",
|
||||
"gsd-core/workflows/discuss-phase/modes/text.md": "b62c9085d4dc2963",
|
||||
"gsd-core/workflows/discuss-phase/templates/checkpoint.json": "e3bc3dca49db59eb",
|
||||
"gsd-core/workflows/discuss-phase/templates/context.md": "6cd929e989fe2b0f",
|
||||
"gsd-core/workflows/discuss-phase/templates/discussion-log.md": "1bbd7703f11128e1",
|
||||
"gsd-core/workflows/do.md": "e55fe97bac5d691f",
|
||||
"gsd-core/workflows/docs-update.md": "fff626c2033d34d2",
|
||||
"gsd-core/workflows/edit-phase.md": "67efb8a73213ea5a",
|
||||
"gsd-core/workflows/eval-review.md": "db4b8795864f104e",
|
||||
"gsd-core/workflows/execute-phase.md": "4ef4e55d34c9c605",
|
||||
"gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "839b0006c551f9fb",
|
||||
"gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028",
|
||||
"gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "cfe19fe951583f3f",
|
||||
"gsd-core/workflows/execute-phase/steps/regression-gate.md": "e40c5eb516a9e052",
|
||||
"gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md": "be84efbd71e1513e",
|
||||
"gsd-core/workflows/execute-plan.md": "5d66d59ce03f88ba",
|
||||
"gsd-core/workflows/explore.md": "eb4a623b5732303f",
|
||||
"gsd-core/workflows/extract-learnings.md": "d1e0a14a9df00195",
|
||||
"gsd-core/workflows/fast.md": "54fe93778b45a7eb",
|
||||
"gsd-core/workflows/forensics.md": "f66bb7b50dcc918a",
|
||||
"gsd-core/workflows/graduation.md": "35ebdec95d86d45b",
|
||||
"gsd-core/workflows/health.md": "bcc07e9610635d49",
|
||||
"gsd-core/workflows/help.md": "5d040504b9ab35e3",
|
||||
"gsd-core/workflows/help/modes/brief.md": "fa2675516b40e2e3",
|
||||
"gsd-core/workflows/help/modes/default.md": "be05e56b2c5ee2c0",
|
||||
"gsd-core/workflows/help/modes/full.md": "579577e8095f8fef",
|
||||
"gsd-core/workflows/help/modes/topic.md": "6e42db16f1568be9",
|
||||
"gsd-core/workflows/import.md": "88e6ef80b6d1db63",
|
||||
"gsd-core/workflows/inbox.md": "91aac6360e1a8672",
|
||||
"gsd-core/workflows/ingest-docs.md": "59224a43a74f0f88",
|
||||
"gsd-core/workflows/insert-phase.md": "653460de68191dd7",
|
||||
"gsd-core/workflows/list-phase-assumptions.md": "2a6b6a5acfb7742c",
|
||||
"gsd-core/workflows/list-seeds.md": "c7026201e80f21ff",
|
||||
"gsd-core/workflows/list-workspaces.md": "2c2f5e466cf97136",
|
||||
"gsd-core/workflows/manager.md": "f0835592a5f4ca39",
|
||||
"gsd-core/workflows/map-codebase.md": "a2c4e72aac41f477",
|
||||
"gsd-core/workflows/milestone-summary.md": "4ad8a311ebf766a8",
|
||||
"gsd-core/workflows/mvp-phase.md": "e459fa1351dbb663",
|
||||
"gsd-core/workflows/new-milestone.md": "417bbc34f965997f",
|
||||
"gsd-core/workflows/new-project.md": "4c7c381c8bc0bd98",
|
||||
"gsd-core/workflows/new-workspace.md": "f907fed6831f3f32",
|
||||
"gsd-core/workflows/next.md": "f2f2210290a02171",
|
||||
"gsd-core/workflows/node-repair.md": "07a1628e5a1ff96b",
|
||||
"gsd-core/workflows/note.md": "def2655cebe5c001",
|
||||
"gsd-core/workflows/onboard.md": "9ae63d270f8fd7c0",
|
||||
"gsd-core/workflows/pause-work.md": "9655ec327c5aa307",
|
||||
"gsd-core/workflows/plan-milestone-gaps.md": "3be3a097114fdde6",
|
||||
"gsd-core/workflows/plan-phase.md": "b1643a49267f3f01",
|
||||
"gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "4099ef6d0868de60",
|
||||
"gsd-core/workflows/plan-phase/steps/prd-express-path.md": "af3029d3c899f4f2",
|
||||
"gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "e9de7a96bbfff261",
|
||||
"gsd-core/workflows/plan-review-convergence.md": "b09378bcc5571ca4",
|
||||
"gsd-core/workflows/plant-seed.md": "4f51d8d267fd56a5",
|
||||
"gsd-core/workflows/pr-branch.md": "8f6db372a275f7b1",
|
||||
"gsd-core/workflows/profile-user.md": "aeb21d7386c4a41e",
|
||||
"gsd-core/workflows/progress.md": "b482c9befd4af94a",
|
||||
"gsd-core/workflows/quick.md": "668e7e8370ba9992",
|
||||
"gsd-core/workflows/reapply-patches.md": "6466079b57163be0",
|
||||
"gsd-core/workflows/remove-phase.md": "7cc144da60d95241",
|
||||
"gsd-core/workflows/remove-workspace.md": "53e9575a8c4fb411",
|
||||
"gsd-core/workflows/resume-project.md": "20de3d2834ee581a",
|
||||
"gsd-core/workflows/review.md": "5ea70bc48614f68f",
|
||||
"gsd-core/workflows/scan.md": "edba4c0bac5727a6",
|
||||
"gsd-core/workflows/secure-phase.md": "265b3244a74a9bdd",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
"gsd-core/workflows/settings-advanced.md": "d148d5233169883f",
|
||||
"gsd-core/workflows/settings-integrations.md": "b03bdf37b4f5310b",
|
||||
"gsd-core/workflows/settings.md": "c24424577729f318",
|
||||
"gsd-core/workflows/ship.md": "4d0fb3bb33f286f8",
|
||||
"gsd-core/workflows/sketch-wrap-up.md": "47cb472ba0445c10",
|
||||
"gsd-core/workflows/sketch.md": "d4de5174c8242425",
|
||||
"gsd-core/workflows/smart-entry.md": "3fe82b4a6e67d0cf",
|
||||
"gsd-core/workflows/spec-phase.md": "b35b7733e8ece267",
|
||||
"gsd-core/workflows/spike-wrap-up.md": "496a6afc08c42a8d",
|
||||
"gsd-core/workflows/spike.md": "3b2a826377c451ff",
|
||||
"gsd-core/workflows/stats.md": "75618011901cebce",
|
||||
"gsd-core/workflows/sync-skills.md": "452c01c7a05b238c",
|
||||
"gsd-core/workflows/thread.md": "9d9cf44f7f6d788e",
|
||||
"gsd-core/workflows/transition.md": "18f610382e9641f6",
|
||||
"gsd-core/workflows/ui-phase.md": "4c8adbabd9f71a78",
|
||||
"gsd-core/workflows/ui-review.md": "108e094e6aa085f6",
|
||||
"gsd-core/workflows/ultraplan-phase.md": "e759430ce77b45db",
|
||||
"gsd-core/workflows/undo.md": "e1eeffa679786c8a",
|
||||
"gsd-core/workflows/update.md": "c276a4f628f4b948",
|
||||
"gsd-core/workflows/validate-phase.md": "96c5ee354b72af56",
|
||||
"gsd-core/workflows/verify-phase.md": "5ee79da8a9e787b3",
|
||||
"gsd-core/workflows/verify-work.md": "25c8dd4c6ca6b5ae",
|
||||
"hooks/gsd-check-update-worker.js": "a530efdb5fdc0da3",
|
||||
"hooks/gsd-check-update.js": "25cde66a12d6b886",
|
||||
"hooks/gsd-config-reload.js": "96546e0e8bb47904",
|
||||
"hooks/gsd-context-monitor.js": "ecbe9747e4a442e0",
|
||||
"hooks/gsd-cursor-post-tool.js": "8a8a249c0642cc71",
|
||||
"hooks/gsd-cursor-session-start.js": "05a14e903c5edafa",
|
||||
"hooks/gsd-ensure-canonical-path.js": "b4b3b88a0e493b16",
|
||||
"hooks/gsd-graphify-update.sh": "e4c6e14fe6ad64ff",
|
||||
"hooks/gsd-phase-boundary.sh": "32739d5fbe0d0a1c",
|
||||
"hooks/gsd-prompt-guard.js": "a749b8cb2c5248de",
|
||||
"hooks/gsd-read-guard.js": "9e423cd03e2d1b16",
|
||||
"hooks/gsd-read-injection-scanner.js": "00d2449afefd2e5f",
|
||||
"hooks/gsd-session-state.sh": "e54379ba86bf1b6d",
|
||||
"hooks/gsd-statusline.js": "7c315416ffc99a9a",
|
||||
"hooks/gsd-update-banner.js": "b457746cb76c1957",
|
||||
"hooks/gsd-validate-commit.sh": "bf5dd61d33cb3a38",
|
||||
"hooks/gsd-workflow-guard.js": "59b46a74d19d58d3",
|
||||
"hooks/gsd-worktree-path-guard.js": "02be1bb504b22eb5",
|
||||
"hooks/lib/git-cmd.js": "268ba15992ca0b23",
|
||||
"hooks/lib/gsd-graphify-rebuild.sh": "66af89601074d2a9",
|
||||
"hooks/managed-hooks-registry.cjs": "ea876b1ec185173e",
|
||||
"package.json": "dbf8353f77358bc1",
|
||||
"scripts/changeset/README.md": "86ff89331dfd94b2",
|
||||
"scripts/changeset/cli.cjs": "68f92a344b199271",
|
||||
"scripts/changeset/github-release-notes.cjs": "795677f0c009b132",
|
||||
"scripts/changeset/lint.cjs": "0066faed159154f0",
|
||||
"scripts/changeset/new.cjs": "4991e21fd17f5541",
|
||||
"scripts/changeset/parse.cjs": "f9a949cbcab56445",
|
||||
"scripts/changeset/render.cjs": "e47bc3e1587c3cae",
|
||||
"scripts/changeset/serialize.cjs": "ac0b8fe6f87cdb0e",
|
||||
"scripts/fix-slash-commands.cjs": "0519742531ff3529",
|
||||
"scripts/gen-capability-registry.cjs": "c52201ff4d1c2cd7",
|
||||
"scripts/gen-loop-host-contract.cjs": "c7f15237234811a0",
|
||||
"scripts/lib/allowlist-ratchet.cjs": "ffaceaac3efc2660",
|
||||
"scripts/lib/cli-exit.cjs": "612d0c372c75b7e7"
|
||||
}
|
||||
@@ -80,7 +80,9 @@ const PKG_VERSION = require('../package.json').version;
|
||||
// (install-minimal-hooks, sh-hook-paths, codex-config, etc.). Matched by basename.
|
||||
// settings.json = Claude/Antigravity/Augment/etc. hook surface; hooks.json =
|
||||
// Codex/Cursor hook surface — both embed the platform-varying node-runner command.
|
||||
const HOOK_CONFIG_FILES = new Set(['settings.json', 'hooks.json']);
|
||||
// settings.local.json = Claude LOCAL hook surface (#338): same platform-varying
|
||||
// node-runner command as settings.json, so excluded for the same reason (#2086).
|
||||
const HOOK_CONFIG_FILES = new Set(['settings.json', 'settings.local.json', 'hooks.json']);
|
||||
|
||||
// Path prefixes excluded from the parity manifest. `gsd-core/bin/lib/` holds the
|
||||
// tsc-built runtime artifacts (compiled from src/*.cts) that the install COPIES
|
||||
@@ -194,3 +196,53 @@ for (const runtime of runtimes) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// #2086 (EoS/claude): claude is the reference host and the ONLY runtime with a
|
||||
// distinct LOCAL "legacy flat-commands" layout (commands/gsd-*.md + agents/gsd-*.md).
|
||||
// The loop above asserts the GLOBAL skills layout; this asserts the LOCAL
|
||||
// commands/agents layout is byte-identical too, so folding claude's
|
||||
// `runtime === 'claude'` branches into descriptor-driven hostBehaviors cannot
|
||||
// silently change the local install output (AC1: "both scopes"). NOTE: the
|
||||
// settings.local.json ROUTING itself is excluded here (platform-varying node-runner
|
||||
// path) — that dimension is covered directly by install.test.cjs's #338 suite.
|
||||
test('golden parity — claude (local legacy layout)', async (t) => {
|
||||
if (process.platform === 'win32') {
|
||||
t.skip('install output is platform-specific on Windows (backslash paths); parity is asserted on macOS + Linux');
|
||||
return;
|
||||
}
|
||||
const { configDir, root } = runMinimalInstall({ runtime: 'claude', scope: 'local' });
|
||||
let actual;
|
||||
try {
|
||||
actual = buildParityManifest(configDir, root);
|
||||
} finally {
|
||||
cleanup(root);
|
||||
}
|
||||
|
||||
const fixturePath = path.join(FIXTURE_DIR, 'claude-local.json');
|
||||
|
||||
if (UPDATE) {
|
||||
fs.writeFileSync(fixturePath, JSON.stringify(actual, null, 2) + '\n', 'utf8');
|
||||
process.stdout.write(` [UPDATE] claude-local: wrote ${Object.keys(actual).length} file hashes → ${fixturePath}\n`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fs.existsSync(fixturePath)) {
|
||||
assert.fail(
|
||||
`Golden fixture missing for claude-local: ${fixturePath}\n` +
|
||||
'Run UPDATE_GOLDEN=1 node --test tests/golden-install-parity.test.cjs to capture.',
|
||||
);
|
||||
}
|
||||
|
||||
const golden = JSON.parse(fs.readFileSync(fixturePath, 'utf8'));
|
||||
const added = Object.keys(actual).filter(k => !(k in golden));
|
||||
const removed = Object.keys(golden).filter(k => !(k in actual));
|
||||
const changed = Object.keys(actual).filter(k => k in golden && actual[k] !== golden[k]);
|
||||
if (added.length || removed.length || changed.length) {
|
||||
const lines = ['Parity mismatch for claude-local:'];
|
||||
if (added.length) lines.push(` added (${added.length}): ${added.join(', ')}`);
|
||||
if (removed.length) lines.push(` removed (${removed.length}): ${removed.join(', ')}`);
|
||||
if (changed.length) lines.push(` changed (${changed.length}): ${changed.join(', ')}`);
|
||||
lines.push('Run UPDATE_GOLDEN=1 to recapture if the change is intentional.');
|
||||
assert.deepEqual(actual, golden, lines.join('\n'));
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user