@opengsd/gsd-core 1.20.0 → 1.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +33 -2
- package/agents/gsd-executor.md +1 -1
- package/agents/gsd-plan-checker.md +1 -1
- package/agents/gsd-planner.md +3 -3
- package/agents/gsd-verifier.md +2 -2
- package/bin/install.js +265 -161
- package/commands/gsd/autonomous.md +1 -1
- package/commands/gsd/new-milestone.md +2 -2
- package/commands/gsd/new-project.md +2 -2
- package/gsd-core/bin/gsd-tools.cjs +554 -823
- package/gsd-core/bin/lib/capability-activation.cjs +8 -2
- package/gsd-core/bin/lib/capability-command-router.cjs +6 -2
- package/gsd-core/bin/lib/capability-ledger.cjs +1 -1
- package/gsd-core/bin/lib/capability-lock.cjs +19 -27
- package/gsd-core/bin/lib/capability-registry.cjs +74 -71
- package/gsd-core/bin/lib/capability-validator.cjs +1 -0
- package/gsd-core/bin/lib/capability-writer.cjs +7 -4
- package/gsd-core/bin/lib/claude-orchestration-command-router.cjs +142 -62
- package/gsd-core/bin/lib/claude-orchestration.cjs +214 -122
- package/gsd-core/bin/lib/cli-surface.cjs +545 -0
- package/gsd-core/bin/lib/command-arg-projection.cjs +356 -103
- package/gsd-core/bin/lib/commands.cjs +99 -2
- package/gsd-core/bin/lib/config-file-read.cjs +85 -0
- package/gsd-core/bin/lib/config-loader.cjs +23 -15
- package/gsd-core/bin/lib/config.cjs +139 -101
- package/gsd-core/bin/lib/configuration.cjs +41 -2
- package/gsd-core/bin/lib/criterion-baseline.cjs +346 -0
- package/gsd-core/bin/lib/criterion-replay.cjs +380 -0
- package/gsd-core/bin/lib/estimate-cli.cjs +5 -1
- package/gsd-core/bin/lib/gap-checker.cjs +12 -1
- package/gsd-core/bin/lib/gate-evaluation-scope.cjs +593 -27
- package/gsd-core/bin/lib/gate-phase-context.cjs +16 -4
- package/gsd-core/bin/lib/graphify.cjs +19 -2
- package/gsd-core/bin/lib/gsd-tool-dir.cjs +134 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +5 -1
- package/gsd-core/bin/lib/init.cjs +20 -18
- package/gsd-core/bin/lib/install-effort-resolver.cjs +24 -4
- package/gsd-core/bin/lib/install-engine.cjs +20 -28
- package/gsd-core/bin/lib/install-fs-adapter.cjs +5 -1
- package/gsd-core/bin/lib/install-manifest-record.cjs +86 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +33 -3
- package/gsd-core/bin/lib/install-profiles.cjs +4 -2
- package/gsd-core/bin/lib/io.cjs +6 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +62 -31
- package/gsd-core/bin/lib/markdown-table.cjs +39 -1
- package/gsd-core/bin/lib/milestone.cjs +8 -6
- package/gsd-core/bin/lib/model-resolver.cjs +8 -4
- package/gsd-core/bin/lib/phase-entry-grammar.cjs +14 -0
- package/gsd-core/bin/lib/phase-id.cjs +26 -9
- package/gsd-core/bin/lib/phase-locator.cjs +8 -3
- package/gsd-core/bin/lib/phase.cjs +170 -45
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +46 -23
- package/gsd-core/bin/lib/planning-snapshot.cjs +10 -6
- package/gsd-core/bin/lib/planning-workspace.cjs +20 -10
- package/gsd-core/bin/lib/profile-output.cjs +9 -6
- package/gsd-core/bin/lib/project-context.cjs +439 -0
- package/gsd-core/bin/lib/project-root.cjs +9 -230
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +1 -1
- package/gsd-core/bin/lib/report-parser.cjs +16 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +357 -50
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +24 -5
- package/gsd-core/bin/lib/roadmap.cjs +2 -2
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +317 -134
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +81 -3
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +6 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +279 -8
- package/gsd-core/bin/lib/runtime-slash.cjs +7 -2
- package/gsd-core/bin/lib/shell-command-projection.cjs +1010 -10
- package/gsd-core/bin/lib/stale-bake-guard.cjs +4 -1
- package/gsd-core/bin/lib/state-transition.cjs +103 -46
- package/gsd-core/bin/lib/state.cjs +448 -63
- package/gsd-core/bin/lib/surface.cjs +4 -7
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +10 -3
- package/gsd-core/bin/lib/verify-command-router.cjs +6 -1
- package/gsd-core/bin/lib/verify.cjs +49 -6
- package/gsd-core/bin/lib/workstream-inventory.cjs +4 -4
- package/gsd-core/bin/lib/worktree-base-ref.cjs +32 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +8 -111
- package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
- package/gsd-core/references/autonomous-smart-discuss.md +99 -18
- package/gsd-core/references/checkpoints.md +1 -1
- package/gsd-core/references/planner-guidance.md +11 -0
- package/gsd-core/references/planner-human-verify-mode.md +1 -1
- package/gsd-core/references/planner-reversibility.md +2 -0
- package/gsd-core/references/planner-verify-command-grounding.md +22 -6
- package/gsd-core/workflows/autonomous.md +4 -4
- package/gsd-core/workflows/code-review-fix.md +5 -7
- package/gsd-core/workflows/execute-phase.md +21 -7
- package/gsd-core/workflows/plan-phase/steps/plan-pre-dispatch.md +101 -0
- package/gsd-core/workflows/plan-phase.md +18 -19
- package/hooks/dist/gsd-agent-isolation-guard.js +4 -3
- package/hooks/dist/gsd-config-reload.js +2 -2
- package/hooks/dist/gsd-context-monitor.js +2 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +9 -6
- package/hooks/dist/gsd-read-injection-scanner.js +2 -2
- package/hooks/dist/gsd-secret-read-guard.js +141 -6
- package/hooks/dist/gsd-statusline.js +3 -2
- package/hooks/dist/gsd-workflow-guard.js +2 -1
- package/hooks/dist/lib/config-read.js +49 -0
- package/hooks/dist/lib/git-cmd.js +3 -1
- package/hooks/dist/lib/git-head-tree.js +474 -0
- package/hooks/dist/lib/isolation-sentinel.js +168 -13
- package/hooks/dist/lib/secret-read-exemptions.js +161 -0
- package/hooks/gsd-agent-isolation-guard.js +4 -3
- package/hooks/gsd-config-reload.js +2 -2
- package/hooks/gsd-context-monitor.js +2 -1
- package/hooks/gsd-cursor-subagent-start.js +9 -6
- package/hooks/gsd-read-injection-scanner.js +2 -2
- package/hooks/gsd-secret-read-guard.js +141 -6
- package/hooks/gsd-statusline.js +3 -2
- package/hooks/gsd-workflow-guard.js +2 -1
- package/hooks/lib/config-read.js +49 -0
- package/hooks/lib/git-cmd.js +3 -1
- package/hooks/lib/git-head-tree.js +474 -0
- package/hooks/lib/isolation-sentinel.js +168 -13
- package/hooks/lib/secret-read-exemptions.js +161 -0
- package/package.json +7 -2
- package/pi/package-agents/gsd-executor.md +1 -1
- package/pi/package-agents/gsd-plan-checker.md +1 -1
- package/pi/package-agents/gsd-planner.md +3 -3
- package/pi/package-agents/gsd-verifier.md +2 -2
- package/pi/package-agents/manifest.json +4 -4
- package/pi/package-skills/gsd-autonomous/SKILL.md +1 -1
- package/pi/package-skills/gsd-new-milestone/SKILL.md +1 -1
- package/pi/package-skills/gsd-new-project/SKILL.md +1 -1
- package/scripts/changeset/cli.cjs +2 -2
- package/scripts/changeset/lint.cjs +28 -3
- package/scripts/changeset/serialize.cjs +12 -2
- package/scripts/ci-compact-v8-coverage.cjs +204 -0
- package/scripts/ci-fix-next-rerun.cjs +149 -0
- package/scripts/ci-next-health.cjs +52 -9
- package/scripts/ci-test-scope.cjs +77 -71
- package/scripts/command-contract-helpers.cjs +56 -7
- package/scripts/docs-guard-registry.cjs +3 -0
- package/scripts/gen-cli-surface-manifest.cjs +287 -0
- package/scripts/gen-platform-conformance-tier.cjs +258 -19
- package/scripts/lib/macos-conformance-tier.generated.cjs +59 -47
- package/scripts/lib/platform-conformance-tier.generated.cjs +23 -64
- package/scripts/lib/shellcheck-fetch.cjs +71 -4
- package/scripts/lint-removed-but-needed.cjs +562 -50
- package/scripts/merge-commit-pr.cjs +186 -0
- package/scripts/release-ancestry.cjs +86 -0
- package/scripts/release-notes/changelog-release-notes.cjs +345 -0
- package/scripts/release-notes/discord-release-summary.cjs +82 -1
- package/scripts/release-pipeline-pr.cjs +92 -0
- package/scripts/require-issue-link-policy.cjs +18 -9
- package/scripts/sync-next-version.cjs +11 -2
- package/scripts/timeout-evidence-from-ci.cjs +964 -0
- package/skills/gsd-autonomous/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-new-project/SKILL.md +1 -1
- package/vscode/package.json +1 -1
package/bin/install.js
CHANGED
|
@@ -623,6 +623,8 @@ const {
|
|
|
623
623
|
stageSkillsForRuntimeAsSkills,
|
|
624
624
|
} = require(path.join(_gsdLibDir, 'install-profiles.cjs'));
|
|
625
625
|
// ADR-857 phase 4c: load capability registry (optional; missing → falls back to undefined)
|
|
626
|
+
// #5759: bounded config.json reader shared with the runtime (non-blocking open, regular file, 1 MiB cap).
|
|
627
|
+
const { readConfigFileBounded } = require(path.join(_gsdLibDir, 'config-file-read.cjs'));
|
|
626
628
|
let _capabilityRegistry;
|
|
627
629
|
try {
|
|
628
630
|
_capabilityRegistry = require(path.join(_gsdLibDir, 'capability-registry.cjs'));
|
|
@@ -789,7 +791,9 @@ const {
|
|
|
789
791
|
applyInstallerMigrationPlan,
|
|
790
792
|
discoverInstallerMigrations,
|
|
791
793
|
MANIFEST_SCHEMA_VERSION,
|
|
794
|
+
planInstallerMigrations,
|
|
792
795
|
readInstallManifest,
|
|
796
|
+
readInstallState,
|
|
793
797
|
recordHostGeneration,
|
|
794
798
|
runInstallerMigrations,
|
|
795
799
|
} = require(path.join(_gsdLibDir, 'installer-migrations.cjs'));
|
|
@@ -810,6 +814,19 @@ const {
|
|
|
810
814
|
} = require(path.join(_gsdLibDir, 'surface.cjs'));
|
|
811
815
|
const {
|
|
812
816
|
assertDestWithinConfigHome,
|
|
817
|
+
resolveRuntimeArtifactDestination,
|
|
818
|
+
resolveRuntimeArtifactInstallRoot,
|
|
819
|
+
plannedActionsMutateCodexHooksInputs,
|
|
820
|
+
shouldInstallCombinedFamily,
|
|
821
|
+
isLegacyFlatLocalInstall,
|
|
822
|
+
shouldInstallStandaloneAgents,
|
|
823
|
+
codexAgentRolesEnabled,
|
|
824
|
+
copyWithPathReplacementSymlinkRefusalMessage,
|
|
825
|
+
installRuntimeArtifactsSymlinkRefusalMessage,
|
|
826
|
+
installOpencodeFamilySkillsSymlinkRefusalMessage,
|
|
827
|
+
installAgentsKindStandaloneSymlinkRefusalMessage,
|
|
828
|
+
installCodexConfigSymlinkRefusalMessage,
|
|
829
|
+
installCodexAgentTomlSymlinkRefusalMessage,
|
|
813
830
|
createRuntimeArtifactInstallPlan,
|
|
814
831
|
createRuntimeArtifactUninstallPlan,
|
|
815
832
|
} = require(path.join(_gsdLibDir, 'runtime-artifact-install-plan.cjs'));
|
|
@@ -879,6 +896,126 @@ function _resolveUserArtifactStagingRoot(configDir) {
|
|
|
879
896
|
return stagingRoot;
|
|
880
897
|
}
|
|
881
898
|
|
|
899
|
+
function isSkillsRuntimeInstall(runtime, scope) {
|
|
900
|
+
const isGlobal = scope === 'global';
|
|
901
|
+
const behaviors = hostBehaviorsFor(runtime);
|
|
902
|
+
if (isLegacyFlatLocalInstall(behaviors, scope) && !isGlobal) return false; // legacy flat local path (descriptor-driven; #2086)
|
|
903
|
+
// #2875 Part 2 defect fix: a runtime whose LOCAL commands are embedded in a
|
|
904
|
+
// rules file rather than materialized as files (hostBehaviors.localCommandsViaRules
|
|
905
|
+
// — cline is the only declarant, capabilities/cline/capability.json) must not
|
|
906
|
+
// flip into this skills/commands-reporting branch merely because its local
|
|
907
|
+
// artifactLayout now also declares an `agents` kind (#2875 Part 2 cline-local
|
|
908
|
+
// agents regression fix). That branch's own verification reporting expects a
|
|
909
|
+
// skills/ or commands/ directory this runtime never writes locally and would
|
|
910
|
+
// spuriously fail; the `localCommandsViaRules` branch below (unchanged
|
|
911
|
+
// messaging) and the unconditional agents-materialization block further down
|
|
912
|
+
// (installAgentsKindStandalone) already cover this runtime/scope correctly.
|
|
913
|
+
if (!isGlobal && behaviors.localCommandsViaRules) return false;
|
|
914
|
+
const cap = _capabilityRegistry && _capabilityRegistry.runtimes && _capabilityRegistry.runtimes[runtime];
|
|
915
|
+
const layout = cap && cap.runtime && cap.runtime.artifactLayout;
|
|
916
|
+
if (!layout) return false;
|
|
917
|
+
const scopeLayout = isGlobal ? layout.global : layout.local;
|
|
918
|
+
return Array.isArray(scopeLayout) && scopeLayout.length > 0;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
function preflightInstallSymlinkDestinations(runtime, configDir, scope, installMode, agentProfile, installerMigrations) {
|
|
922
|
+
const check = (installRoot, destination, message) => {
|
|
923
|
+
if (hasExistingSymlinkBetween(path.resolve(installRoot), destination, { allowOptInFollow: isSymlinkedDestOptIn() })) {
|
|
924
|
+
throw new Error(message);
|
|
925
|
+
}
|
|
926
|
+
};
|
|
927
|
+
|
|
928
|
+
const coreDir = assertDestWithinConfigHome(configDir, path.join(configDir, 'gsd-core'));
|
|
929
|
+
check(configDir, coreDir, copyWithPathReplacementSymlinkRefusalMessage(coreDir, configDir));
|
|
930
|
+
|
|
931
|
+
const layout = resolveRuntimeArtifactLayout(runtime, configDir, scope, _installedCapabilityRegistry);
|
|
932
|
+
const behaviors = hostBehaviorsFor(runtime);
|
|
933
|
+
const combinedFamily = shouldInstallCombinedFamily(behaviors);
|
|
934
|
+
const legacyFlatLocal = isLegacyFlatLocalInstall(behaviors, scope);
|
|
935
|
+
const skillsRuntime = isSkillsRuntimeInstall(runtime, scope);
|
|
936
|
+
const standaloneAgents = shouldInstallStandaloneAgents(Boolean(behaviors.pluginOnlyInstall), skillsRuntime);
|
|
937
|
+
|
|
938
|
+
for (const kind of layout.kinds) {
|
|
939
|
+
// These flat writers have no write-time trust check to front-run.
|
|
940
|
+
// Keep their existing behavior here; a new refusal needs separate approval.
|
|
941
|
+
if (combinedFamily && kind.kind === 'commands') continue;
|
|
942
|
+
if (legacyFlatLocal && (kind.kind === 'commands' || kind.kind === 'skills')) continue;
|
|
943
|
+
const installRoot = resolveRuntimeArtifactInstallRoot(configDir, kind);
|
|
944
|
+
const dest = resolveRuntimeArtifactDestination(configDir, kind);
|
|
945
|
+
if (hasExistingSymlinkBetween(path.resolve(installRoot), dest, { allowOptInFollow: isSymlinkedDestOptIn() })) {
|
|
946
|
+
if (combinedFamily && kind.kind === 'skills') {
|
|
947
|
+
throw new Error(installOpencodeFamilySkillsSymlinkRefusalMessage(dest, installRoot));
|
|
948
|
+
}
|
|
949
|
+
if ((standaloneAgents || combinedFamily) && kind.kind === 'agents') {
|
|
950
|
+
throw new Error(installAgentsKindStandaloneSymlinkRefusalMessage(dest, installRoot));
|
|
951
|
+
}
|
|
952
|
+
throw new Error(installRuntimeArtifactsSymlinkRefusalMessage(dest, installRoot));
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
if (behaviors.tomlConfigInstall && codexAgentRolesEnabled(installMode, agentProfile)) {
|
|
957
|
+
const configPath = assertDestWithinConfigHome(configDir, 'config.toml');
|
|
958
|
+
const agentsTomlDir = assertDestWithinConfigHome(configDir, 'agents');
|
|
959
|
+
check(configDir, configPath, installCodexConfigSymlinkRefusalMessage(configDir));
|
|
960
|
+
check(configDir, path.resolve(agentsTomlDir), installCodexConfigSymlinkRefusalMessage(configDir));
|
|
961
|
+
// The layout agents pass prunes gsd-* entries (including TOML symlinks)
|
|
962
|
+
// before installCodexConfig. Its leaf guard sees the replacement state,
|
|
963
|
+
// so front-running that guard here would introduce a new refusal.
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
if (resolveInstallPlan(runtime).installSurface === 'codex-toml') {
|
|
967
|
+
const migrations = installerMigrations || discoverInstallerMigrations({
|
|
968
|
+
migrationsDir: path.join(_gsdLibDir, 'installer-migrations'),
|
|
969
|
+
});
|
|
970
|
+
// Planning is read-only. A pending id alone says nothing about whether
|
|
971
|
+
// this install's hooks/config inputs change (002 can itself be a no-op).
|
|
972
|
+
// Defer only actual mutations of those inputs; the migration executor
|
|
973
|
+
// and reconciler retain authority over publication and write failures.
|
|
974
|
+
// baselineScan intentionally stays off: migration 000 only records,
|
|
975
|
+
// preserves, or prompts; it cannot mutate hooks/config inputs. The real
|
|
976
|
+
// baseline scan and prompt resolution remain with the migration executor.
|
|
977
|
+
const migrationPlan = planInstallerMigrations({ configDir, runtime, scope, migrations });
|
|
978
|
+
if (plannedActionsMutateCodexHooksInputs(migrationPlan.actions)) return;
|
|
979
|
+
const configPath = path.join(configDir, 'config.toml');
|
|
980
|
+
let configContent = fs.existsSync(configPath) ? fs.readFileSync(configPath, 'utf8') : '';
|
|
981
|
+
if (codexAgentRolesEnabled(installMode, agentProfile)) {
|
|
982
|
+
// The managed config block contains no roster-dependent fields (#2406).
|
|
983
|
+
// If that changes, pass the writer's selected roster to both projections.
|
|
984
|
+
configContent = mergeCodexConfigContent(configContent, generateCodexConfigBlock([], configDir));
|
|
985
|
+
}
|
|
986
|
+
const prepared = prepareCodexHooksConfig(configContent);
|
|
987
|
+
// Extended-event cleanup reconciles hooks.json even when SessionStart has
|
|
988
|
+
// no script/runner. Inline/scalar features may leave reconciliation off.
|
|
989
|
+
if (hasEnabledCodexHooksFeature(prepared.content)) {
|
|
990
|
+
hooksSurface.resolveCodexHooksJsonDestination(configDir);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
function prepareCodexAgentConfigContent(source, sourcePath, targetDir) {
|
|
996
|
+
let content = composeWorkflow(source, { sourcePath });
|
|
997
|
+
const codexGsdPath = `${path.resolve(targetDir, 'gsd-core').replace(/\\/g, '/')}/`;
|
|
998
|
+
// Replace full .claude/gsd-core prefix so path resolves to the Codex
|
|
999
|
+
// GSD install before generic .claude → .codex conversion rewrites it.
|
|
1000
|
+
// #5715: one pass, function replacement: a string replacement would expand `$&` in a
|
|
1001
|
+
// config dir, and a second pass would rescan the path the first emitted.
|
|
1002
|
+
content = content.replace(/(?:~|\$HOME)\/\.claude\/gsd-core\//g, () => codexGsdPath);
|
|
1003
|
+
// Route TOML emit through the same full Claude→Codex conversion pipeline
|
|
1004
|
+
// used on the `.md` emit path (#2639). Covers: slash-command rewrites,
|
|
1005
|
+
// $ARGUMENTS → {{GSD_ARGS}}, /clear removal, anchored and bare .claude/
|
|
1006
|
+
// paths, .claudeignore → .codexignore, and standalone "Claude" /
|
|
1007
|
+
// CLAUDE.md neutralization via neutralizeAgentReferences(..., 'AGENTS.md').
|
|
1008
|
+
// #5715: the converter runs after the path above was emitted; shield it from the converter.
|
|
1009
|
+
return runtimeArtifactConversion._shieldEmittedPrefix(content, codexGsdPath, convertClaudeToCodexMarkdown);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
function codexAgentTomlName(file, source) {
|
|
1013
|
+
const { frontmatter } = extractFrontmatterAndBody(source);
|
|
1014
|
+
// Resolve identity from converted frontmatter, matching the header
|
|
1015
|
+
// conversion in prepareCodexAgentConfigContent.
|
|
1016
|
+
return extractFrontmatterField(convertClaudeToCodexMarkdown(frontmatter), 'name') || file.replace(/\.md$/, '');
|
|
1017
|
+
}
|
|
1018
|
+
|
|
882
1019
|
/**
|
|
883
1020
|
* Degrade-not-abort wrapper over `_resolveUserArtifactStagingRoot` — mirrors
|
|
884
1021
|
* install-engine.cts's own `_tryResolveUserArtifactStagingRoot` (kept local
|
|
@@ -2376,10 +2513,11 @@ function convertClaudeAgentToCopilotAgent(content, isGlobal = false) {
|
|
|
2376
2513
|
const name = extractFrontmatterField(frontmatter, 'name') || 'unknown';
|
|
2377
2514
|
const description = extractFrontmatterField(frontmatter, 'description') || '';
|
|
2378
2515
|
const color = extractFrontmatterField(frontmatter, 'color');
|
|
2379
|
-
const toolsRaw = extractFrontmatterField(frontmatter, 'tools') || '';
|
|
2380
2516
|
|
|
2381
|
-
// CONV-04 + CONV-05: Map tools, deduplicate, format as JSON array
|
|
2382
|
-
|
|
2517
|
+
// CONV-04 + CONV-05: Map tools, deduplicate, format as JSON array.
|
|
2518
|
+
// parseFrontmatterTools reads both the inline comma list and the YAML block
|
|
2519
|
+
// list (#5790).
|
|
2520
|
+
const claudeTools = parseFrontmatterTools(frontmatter);
|
|
2383
2521
|
const mappedTools = claudeTools.map(t => convertCopilotToolName(t));
|
|
2384
2522
|
const uniqueTools = [...new Set(mappedTools)];
|
|
2385
2523
|
const toolsArray = uniqueTools.length > 0
|
|
@@ -2659,7 +2797,15 @@ function convertSlashCommandsToTraeSkillMentions(content) {
|
|
|
2659
2797
|
});
|
|
2660
2798
|
}
|
|
2661
2799
|
|
|
2662
|
-
function convertClaudeToTraeMarkdown(content) {
|
|
2800
|
+
function convertClaudeToTraeMarkdown(content, pathPrefix) {
|
|
2801
|
+
return runtimeArtifactConversion._shieldEmittedPrefix(
|
|
2802
|
+
runtimeArtifactConversion._resolveTraeInstructionFileAtPrefix(content, pathPrefix),
|
|
2803
|
+
pathPrefix,
|
|
2804
|
+
convertClaudeToTraeMarkdownBody,
|
|
2805
|
+
);
|
|
2806
|
+
}
|
|
2807
|
+
|
|
2808
|
+
function convertClaudeToTraeMarkdownBody(content) {
|
|
2663
2809
|
let converted = convertSlashCommandsToTraeSkillMentions(filterRuntimeNotesForTarget(content, 'trae'));
|
|
2664
2810
|
converted = converted.replace(/\bBash\(/g, 'Shell(');
|
|
2665
2811
|
converted = converted.replace(/\bEdit\(/g, 'StrReplace(');
|
|
@@ -2719,9 +2865,10 @@ function convertClaudeToTraeMarkdown(content) {
|
|
|
2719
2865
|
converted = converted.replace(/\.\/\.claude\//g, './.trae/');
|
|
2720
2866
|
converted = converted.replace(/\.claude\//g, '.trae/');
|
|
2721
2867
|
// Bare forms (no trailing slash) — after slash forms to avoid double-rewrite.
|
|
2722
|
-
// Use negative lookahead (?![\w-]) to preserve .claude-plugin and .
|
|
2723
|
-
|
|
2724
|
-
converted = converted.replace(
|
|
2868
|
+
// Use negative lookahead (?![\w-]|\.\w) to preserve .claude-plugin, .claudeignore and .claude.json.
|
|
2869
|
+
// #5715: an emitted prefix is shielded by convertClaudeToTraeMarkdown; the skills path runs this before the prefix pass, and the profile maps the own-dir forms.
|
|
2870
|
+
converted = converted.replace(/~\/\.claude(?![\w-]|\.\w)/g, '~/.trae');
|
|
2871
|
+
converted = converted.replace(/\$HOME\/\.claude(?![\w-]|\.\w)/g, () => '$HOME/.trae');
|
|
2725
2872
|
// Environment variable name rewrite
|
|
2726
2873
|
converted = converted.replace(/\bCLAUDE_CONFIG_DIR\b/g, 'TRAE_CONFIG_DIR');
|
|
2727
2874
|
converted = converted.replace(/\*\*Known Claude Code bug \(classifyHandoffIfNeeded\):\*\*[^\n]*\n/g, '');
|
|
@@ -2765,8 +2912,8 @@ function convertClaudeCommandToTraeSkill(content, skillName) {
|
|
|
2765
2912
|
return `${fm}\n${body}`;
|
|
2766
2913
|
}
|
|
2767
2914
|
|
|
2768
|
-
function convertClaudeAgentToTraeAgent(content) {
|
|
2769
|
-
let converted = convertClaudeToTraeMarkdown(content);
|
|
2915
|
+
function convertClaudeAgentToTraeAgent(content, _isGlobal, meta) {
|
|
2916
|
+
let converted = convertClaudeToTraeMarkdown(content, meta && meta.pathPrefix);
|
|
2770
2917
|
|
|
2771
2918
|
const { frontmatter, body } = extractFrontmatterAndBody(converted);
|
|
2772
2919
|
if (!frontmatter) return converted;
|
|
@@ -4050,7 +4197,9 @@ function convertClaudeAgentToCodexAgent(content) {
|
|
|
4050
4197
|
|
|
4051
4198
|
const name = extractFrontmatterField(frontmatter, 'name') || 'unknown';
|
|
4052
4199
|
const description = extractFrontmatterField(frontmatter, 'description') || '';
|
|
4053
|
-
|
|
4200
|
+
// parseFrontmatterTools reads both the inline comma list and the YAML block
|
|
4201
|
+
// list (#5790).
|
|
4202
|
+
const tools = parseFrontmatterTools(frontmatter).join(', ');
|
|
4054
4203
|
|
|
4055
4204
|
const roleHeader = `<codex_agent_role>
|
|
4056
4205
|
role: ${name}
|
|
@@ -6547,15 +6696,15 @@ function resolveSymlinkWriteTarget(filePath) {
|
|
|
6547
6696
|
}
|
|
6548
6697
|
|
|
6549
6698
|
function mergeCodexConfig(configPath, gsdBlock) {
|
|
6550
|
-
// #5301:
|
|
6699
|
+
// #5301: the write lands on the symlink's target, never replaces the link.
|
|
6551
6700
|
configPath = resolveSymlinkWriteTarget(configPath);
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
return;
|
|
6556
|
-
}
|
|
6701
|
+
const existing = fs.existsSync(configPath) ? fs.readFileSync(configPath, 'utf8') : null;
|
|
6702
|
+
atomicWriteFileSync(configPath, mergeCodexConfigContent(existing, gsdBlock));
|
|
6703
|
+
}
|
|
6557
6704
|
|
|
6558
|
-
|
|
6705
|
+
function mergeCodexConfigContent(existing, gsdBlock) {
|
|
6706
|
+
// Case 1: No config.toml — create fresh
|
|
6707
|
+
if (existing === null) return gsdBlock + '\n';
|
|
6559
6708
|
const eol = detectLineEnding(existing);
|
|
6560
6709
|
// #2088 review: the bare `[agents]` table is purged below (Case 2/3 via
|
|
6561
6710
|
// stripLeakedGsdCodexSections) to keep a single managed `[agents]`. Preserve
|
|
@@ -6614,8 +6763,7 @@ function mergeCodexConfig(configPath, gsdBlock) {
|
|
|
6614
6763
|
if (beforeSplit.rest) parts.push(beforeSplit.rest);
|
|
6615
6764
|
parts.push(normalizedGsdBlock);
|
|
6616
6765
|
if (afterTables) parts.push(afterTables);
|
|
6617
|
-
|
|
6618
|
-
return;
|
|
6766
|
+
return parts.join(eol + eol) + eol;
|
|
6619
6767
|
}
|
|
6620
6768
|
|
|
6621
6769
|
// Case 3: No marker — append GSD block
|
|
@@ -6626,7 +6774,7 @@ function mergeCodexConfig(configPath, gsdBlock) {
|
|
|
6626
6774
|
content = normalizedGsdBlock + eol;
|
|
6627
6775
|
}
|
|
6628
6776
|
|
|
6629
|
-
|
|
6777
|
+
return content;
|
|
6630
6778
|
}
|
|
6631
6779
|
|
|
6632
6780
|
/**
|
|
@@ -6811,6 +6959,19 @@ function ensureCodexHooksFeature(configContent) {
|
|
|
6811
6959
|
return { content: configContent + (needsGap ? eol : '') + featuresBlock, ownership: 'section' };
|
|
6812
6960
|
}
|
|
6813
6961
|
|
|
6962
|
+
function prepareCodexHooksConfig(configContent) {
|
|
6963
|
+
// Strip GSD-owned legacy blocks before migrating user hooks (#2698).
|
|
6964
|
+
// Share this exact projection with preflight, including inline/scalar
|
|
6965
|
+
// features values that ensureCodexHooksFeature deliberately preserves.
|
|
6966
|
+
const stripped = stripStaleGsdHookBlocks(configContent);
|
|
6967
|
+
const migrated = migrateCodexHooksMapFormat(stripped);
|
|
6968
|
+
const feature = ensureCodexHooksFeature(migrated);
|
|
6969
|
+
return {
|
|
6970
|
+
content: setManagedCodexHooksOwnership(feature.content, feature.ownership),
|
|
6971
|
+
migrated: migrated !== stripped,
|
|
6972
|
+
};
|
|
6973
|
+
}
|
|
6974
|
+
|
|
6814
6975
|
function hasEnabledCodexHooksFeature(configContent) {
|
|
6815
6976
|
const lineRecords = getTomlLineRecords(configContent);
|
|
6816
6977
|
|
|
@@ -7225,7 +7386,7 @@ function installCodexConfig(targetDir, agentsSrc, sandboxTier = 'codex-agent-san
|
|
|
7225
7386
|
hasExistingSymlinkBetween(resolvedTargetRoot, path.resolve(agentsTomlDir), { allowOptInFollow: symlinkOptIn })
|
|
7226
7387
|
) {
|
|
7227
7388
|
throw new Error(
|
|
7228
|
-
|
|
7389
|
+
installCodexConfigSymlinkRefusalMessage(targetDir),
|
|
7229
7390
|
);
|
|
7230
7391
|
}
|
|
7231
7392
|
fs.mkdirSync(agentsTomlDir, { recursive: true });
|
|
@@ -7263,12 +7424,9 @@ function installCodexConfig(targetDir, agentsSrc, sandboxTier = 'codex-agent-san
|
|
|
7263
7424
|
const agentEntries = fs.readdirSync(agentsSrc).filter(f => f.startsWith('gsd-') && f.endsWith('.md'));
|
|
7264
7425
|
const agents = [];
|
|
7265
7426
|
|
|
7266
|
-
// Compute the Codex GSD install path (absolute, so subagents with empty $HOME work — #820)
|
|
7267
|
-
const codexGsdPath = `${path.resolve(targetDir, 'gsd-core').replace(/\\/g, '/')}/`;
|
|
7268
|
-
|
|
7269
7427
|
for (const file of agentEntries) {
|
|
7270
7428
|
const agentTomlSourcePath = path.join(agentsSrc, file);
|
|
7271
|
-
|
|
7429
|
+
const source = fs.readFileSync(agentTomlSourcePath, 'utf8');
|
|
7272
7430
|
// #2995 (epic #1671 Phase 6.4): Codex embeds each agent's prompt into a
|
|
7273
7431
|
// per-agent `.toml`, reading the source .md independently of the inline
|
|
7274
7432
|
// agent loop — a separate emission path that must strip gsd:section
|
|
@@ -7276,17 +7434,7 @@ function installCodexConfig(targetDir, agentsSrc, sandboxTier = 'codex-agent-san
|
|
|
7276
7434
|
// Found by the exhaustive per-runtime emission sweep in
|
|
7277
7435
|
// tests/agent-fragments-emission.install.test.cjs, not by call-graph
|
|
7278
7436
|
// analysis, which is why that guard is behavioral rather than structural.
|
|
7279
|
-
content =
|
|
7280
|
-
// Replace full .claude/gsd-core prefix so path resolves to the Codex
|
|
7281
|
-
// GSD install before generic .claude → .codex conversion rewrites it.
|
|
7282
|
-
content = content.replace(/~\/\.claude\/gsd-core\//g, codexGsdPath);
|
|
7283
|
-
content = content.replace(/\$HOME\/\.claude\/gsd-core\//g, codexGsdPath);
|
|
7284
|
-
// Route TOML emit through the same full Claude→Codex conversion pipeline
|
|
7285
|
-
// used on the `.md` emit path (#2639). Covers: slash-command rewrites,
|
|
7286
|
-
// $ARGUMENTS → {{GSD_ARGS}}, /clear removal, anchored and bare .claude/
|
|
7287
|
-
// paths, .claudeignore → .codexignore, and standalone "Claude" /
|
|
7288
|
-
// CLAUDE.md neutralization via neutralizeAgentReferences(..., 'AGENTS.md').
|
|
7289
|
-
content = convertClaudeToCodexMarkdown(content);
|
|
7437
|
+
const content = prepareCodexAgentConfigContent(source, agentTomlSourcePath, targetDir);
|
|
7290
7438
|
const { frontmatter } = extractFrontmatterAndBody(content);
|
|
7291
7439
|
// #3897 security review F1 (blocker, post-merge): this loop used to key
|
|
7292
7440
|
// the sandbox/hold decision off ONLY the filename stem while the emitted
|
|
@@ -7301,7 +7449,7 @@ function installCodexConfig(targetDir, agentsSrc, sandboxTier = 'codex-agent-san
|
|
|
7301
7449
|
// for that call; see `deriveCodexSandboxMode`'s doc in
|
|
7302
7450
|
// `codex-agent-toml.cts` for the resolution.
|
|
7303
7451
|
const fileStem = file.replace(/\.md$/, '');
|
|
7304
|
-
const name =
|
|
7452
|
+
const name = codexAgentTomlName(file, source);
|
|
7305
7453
|
const description = extractFrontmatterField(frontmatter, 'description') || '';
|
|
7306
7454
|
|
|
7307
7455
|
agents.push({ name, description: toSingleLine(description) });
|
|
@@ -7333,7 +7481,7 @@ function installCodexConfig(targetDir, agentsSrc, sandboxTier = 'codex-agent-san
|
|
|
7333
7481
|
const agentTomlPath = assertDestWithinConfigHome(agentsTomlDir, `${name}.toml`);
|
|
7334
7482
|
if (hasExistingSymlinkBetween(resolvedTargetRoot, agentTomlPath, { allowOptInFollow: symlinkOptIn })) {
|
|
7335
7483
|
throw new Error(
|
|
7336
|
-
|
|
7484
|
+
installCodexAgentTomlSymlinkRefusalMessage(agentTomlPath),
|
|
7337
7485
|
);
|
|
7338
7486
|
}
|
|
7339
7487
|
fs.writeFileSync(agentTomlPath, tomlContent);
|
|
@@ -7949,7 +8097,9 @@ const RUNTIME_CONTENT_DISPATCH = {
|
|
|
7949
8097
|
},
|
|
7950
8098
|
},
|
|
7951
8099
|
trae: {
|
|
7952
|
-
|
|
8100
|
+
// #5715: the Trae converter shields (and resolves) the emitted prefix itself.
|
|
8101
|
+
mdShieldsPrefix: true,
|
|
8102
|
+
md: (content, ctx) => convertClaudeToTraeMarkdown(content, ctx && ctx.pathPrefix),
|
|
7953
8103
|
js: (content) => {
|
|
7954
8104
|
content = content.replace(/\/gsd:([a-z0-9-]+)/g, (_, commandName) => {
|
|
7955
8105
|
return `/gsd-${commandName}`;
|
|
@@ -8060,7 +8210,7 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand
|
|
|
8060
8210
|
// #2393: honor GSD_ALLOW_SYMLINKED_DEST for intentional user-owned symlink layouts.
|
|
8061
8211
|
if (hasExistingSymlinkBetween(resolvedConfinementRoot, resolvedDestDir, { allowOptInFollow: isSymlinkedDestOptIn() })) {
|
|
8062
8212
|
throw new Error(
|
|
8063
|
-
|
|
8213
|
+
copyWithPathReplacementSymlinkRefusalMessage(destDir, confinementRoot),
|
|
8064
8214
|
);
|
|
8065
8215
|
}
|
|
8066
8216
|
// Use the validated absolute path for all writes below so the gate validates
|
|
@@ -8152,33 +8302,8 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand
|
|
|
8152
8302
|
// balanced masking grammar used by this path and the rewrite engine.
|
|
8153
8303
|
const rewriteGenericPaths = (body) => {
|
|
8154
8304
|
content = body;
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
const localClaudeRegex = /\.\/\.claude\//g;
|
|
8158
|
-
content = content.replace(globalClaudeRegex, pathPrefix);
|
|
8159
|
-
content = content.replace(globalClaudeHomeRegex, pathPrefix);
|
|
8160
|
-
content = content.replace(localClaudeRegex, `./${dirName}/`);
|
|
8161
|
-
// #3544 review (Finding 1 fallout): guarded with the SAME
|
|
8162
|
-
// negative-lookahead convention already used at ~:2859-2860 below
|
|
8163
|
-
// ("preserve .claude-plugin and .claudeignore"). A naive `\b` here
|
|
8164
|
-
// is satisfied by ANY non-word character, including '-' — so for a
|
|
8165
|
-
// --config-dir whose name EXTENDS '.claude' (e.g. '.claude-work',
|
|
8166
|
-
// pathPrefix '$HOME/.claude-work/'), this pass re-matched the
|
|
8167
|
-
// '$HOME/.claude' PREFIX of its own slash-form output (lines above)
|
|
8168
|
-
// and re-appended the full prefix, corrupting every emitted path to
|
|
8169
|
-
// '$HOME/.claude-work-work/...'. Harmless no-op for the literal
|
|
8170
|
-
// default '.claude' (self-replace with an identical string), which
|
|
8171
|
-
// is why this went undetected until a non-default config-dir name
|
|
8172
|
-
// was exercised.
|
|
8173
|
-
content = content.replace(/~\/\.claude(?![\w-])/g, pathPrefix.replace(/\/$/, ''));
|
|
8174
|
-
content = content.replace(/\$HOME\/\.claude(?![\w-])/g, pathPrefix.replace(/\/$/, ''));
|
|
8175
|
-
content = content.replace(/\.\/\.claude\b/g, `./${dirName}`);
|
|
8176
|
-
content = content.replace(/~\/\.qwen\//g, pathPrefix);
|
|
8177
|
-
content = content.replace(/\$HOME\/\.qwen\//g, pathPrefix);
|
|
8178
|
-
content = content.replace(/\.\/\.qwen\//g, `./${dirName}/`);
|
|
8179
|
-
content = content.replace(/~\/\.hermes\//g, pathPrefix);
|
|
8180
|
-
content = content.replace(/\$HOME\/\.hermes\//g, pathPrefix);
|
|
8181
|
-
content = content.replace(/\.\/\.hermes\//g, `./${dirName}/`);
|
|
8305
|
+
// #5715: every generic source form in ONE pass (no rescan of the emitted prefix).
|
|
8306
|
+
content = runtimeArtifactConversion._rewriteSpecTreeConfigDirForms(content, pathPrefix, dirName);
|
|
8182
8307
|
// #3544: restore @-file-reference lines to the tilde form Claude Code
|
|
8183
8308
|
// actually expands — the SAME correction #3133 already applies to
|
|
8184
8309
|
// skill/command bodies via _applyRuntimeRewrites's 'claude' case (see
|
|
@@ -8215,7 +8340,15 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand
|
|
|
8215
8340
|
content = normalizeAgentBodyForRuntime(content, runtime, readGsdCommandNames());
|
|
8216
8341
|
|
|
8217
8342
|
// Apply per-runtime .md converter (if any)
|
|
8218
|
-
|
|
8343
|
+
// #5715: the generic pass above emitted `pathPrefix`; shield it from the converter
|
|
8344
|
+
// (a converter that skips the generic pass emitted nothing to shield).
|
|
8345
|
+
if (dispatch.md) {
|
|
8346
|
+
content = runtimeArtifactConversion._shieldEmittedPrefix(
|
|
8347
|
+
content,
|
|
8348
|
+
dispatch.mdSkipGenericRewrite || dispatch.mdShieldsPrefix ? undefined : pathPrefix,
|
|
8349
|
+
(shielded) => dispatch.md(shielded, ctx),
|
|
8350
|
+
);
|
|
8351
|
+
}
|
|
8219
8352
|
|
|
8220
8353
|
// Re-run attribution after converter for runtimes that need it (copilot, antigravity)
|
|
8221
8354
|
if (dispatch.mdReattributeAfter) content = processAttribution(content, getCommitAttribution(runtime));
|
|
@@ -10087,8 +10220,8 @@ const MANIFEST_NAME = 'gsd-file-manifest.json';
|
|
|
10087
10220
|
* Compute SHA256 hash of file contents
|
|
10088
10221
|
*/
|
|
10089
10222
|
function fileHash(filePath) {
|
|
10090
|
-
|
|
10091
|
-
return
|
|
10223
|
+
// #5683: single owner of the per-file hash, shared with `effort sync`.
|
|
10224
|
+
return require('../gsd-core/bin/lib/install-manifest-record.cjs').fileHash(filePath);
|
|
10092
10225
|
}
|
|
10093
10226
|
|
|
10094
10227
|
/**
|
|
@@ -11125,26 +11258,6 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11125
11258
|
warnIfForeignAgentDest(runtime, targetDir, _installScopeId, Boolean(explicitConfigDir));
|
|
11126
11259
|
}
|
|
11127
11260
|
|
|
11128
|
-
// #2875 (#1874-F19 anti-inertness, test-matrix C7): recover any user
|
|
11129
|
-
// artifact orphaned by a PRIOR install run that died between staging and
|
|
11130
|
-
// its own restore/discard, BEFORE this run's own preserve step stages
|
|
11131
|
-
// anything new. This is the production entry point every install() call
|
|
11132
|
-
// reaches — the only place this phase's durability fix is complete rather
|
|
11133
|
-
// than merely callable (40-design.md "The inertness trap this design must
|
|
11134
|
-
// avoid" / #1879-F15). Runs for every runtime, ahead of both the
|
|
11135
|
-
// layout-driven path's _runLegacyInstallMigrations (site 1, inside
|
|
11136
|
-
// installRuntimeArtifacts) and this function's own mainline gsd-core copy
|
|
11137
|
-
// (site 4, below).
|
|
11138
|
-
// #2875 defect fix: DEGRADE, never abort install, when the staging root
|
|
11139
|
-
// itself cannot be resolved — skip this recovery pass rather than throw
|
|
11140
|
-
// out of install() before it does anything.
|
|
11141
|
-
{
|
|
11142
|
-
const _installEntryStagingRoot = _tryResolveUserArtifactStagingRoot(targetDir);
|
|
11143
|
-
if (_installEntryStagingRoot !== null) {
|
|
11144
|
-
recoverOrphanedUserArtifacts(_installEntryStagingRoot, targetDir);
|
|
11145
|
-
}
|
|
11146
|
-
}
|
|
11147
|
-
|
|
11148
11261
|
const locationLabel = isGlobal
|
|
11149
11262
|
? targetDir.replace(os.homedir(), '~')
|
|
11150
11263
|
: targetDir.replace(process.cwd(), '.');
|
|
@@ -11226,7 +11339,10 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11226
11339
|
// #5503: Codex registers agent roles in config.toml. That used to be gated on "minimal mode"
|
|
11227
11340
|
// because minimal staged no agents; gate it on whether the resolved profile stages any.
|
|
11228
11341
|
const _codexAgentRolesEnabled = () =>
|
|
11229
|
-
|
|
11342
|
+
codexAgentRolesEnabled(_effectiveInstallMode, _resolvedProfile);
|
|
11343
|
+
// Preserve rollback eligibility from the original profile, before preflight
|
|
11344
|
+
// resolves the committed surface for the later writers.
|
|
11345
|
+
const _codexPreConfigRollbackArmed = _codexAgentRolesEnabled();
|
|
11230
11346
|
const persistActiveProfileMarker = () => {
|
|
11231
11347
|
try {
|
|
11232
11348
|
writeActiveProfile(targetDir, _activeProfileName);
|
|
@@ -11251,6 +11367,36 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11251
11367
|
localDirName: hostBehaviorsFor(runtime).localTargetIsProjectRoot === true ? undefined : getDirName(runtime),
|
|
11252
11368
|
});
|
|
11253
11369
|
|
|
11370
|
+
// Resolve the committed surface before preflight and keep that selection
|
|
11371
|
+
// for every subsequent writer, including Codex's staged agent TOMLs.
|
|
11372
|
+
if (isSkillsRuntimeInstall(runtime, _installScopeId)) {
|
|
11373
|
+
const _committedSurface = readSurface(targetDir);
|
|
11374
|
+
if (_committedSurface) {
|
|
11375
|
+
_resolvedProfile = resolveSurface(
|
|
11376
|
+
targetDir,
|
|
11377
|
+
loadSkillsManifest(_commandsDir),
|
|
11378
|
+
undefined,
|
|
11379
|
+
_installedCapabilityRegistry,
|
|
11380
|
+
_committedSurface,
|
|
11381
|
+
);
|
|
11382
|
+
}
|
|
11383
|
+
}
|
|
11384
|
+
// Preflight's role gate and _stageAgents use the same resolved profile;
|
|
11385
|
+
// core's agent roles follow #5503's profile-derived selection too.
|
|
11386
|
+
preflightInstallSymlinkDestinations(runtime, targetDir, _installScopeId, _effectiveInstallMode, _resolvedProfile, options.installerMigrations);
|
|
11387
|
+
|
|
11388
|
+
// #2875 (#1874-F19 anti-inertness, test-matrix C7): recover any user
|
|
11389
|
+
// artifact orphaned by a PRIOR install run that died between staging and
|
|
11390
|
+
// its own restore/discard, BEFORE this run's own preserve step stages
|
|
11391
|
+
// anything new. Destination preflight runs before this recovery write;
|
|
11392
|
+
// checks for destinations replaced before admission stay with the writers.
|
|
11393
|
+
{
|
|
11394
|
+
const _installEntryStagingRoot = _tryResolveUserArtifactStagingRoot(targetDir);
|
|
11395
|
+
if (_installEntryStagingRoot !== null) {
|
|
11396
|
+
recoverOrphanedUserArtifacts(_installEntryStagingRoot, targetDir);
|
|
11397
|
+
}
|
|
11398
|
+
}
|
|
11399
|
+
|
|
11254
11400
|
// runtimeLabel is now the single-source getRuntimeLabel lookup (ADR-1239
|
|
11255
11401
|
// Phase B / #1679) — collapses the prior 16-line assignment chain.
|
|
11256
11402
|
const runtimeLabel = getRuntimeLabel(runtime);
|
|
@@ -11601,7 +11747,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11601
11747
|
// The full restoreCodexSnapshot() (defined inside the config block) additionally
|
|
11602
11748
|
// handles config.toml and the staged hooks/ tree, which are not yet touched
|
|
11603
11749
|
// at this point in the pipeline.
|
|
11604
|
-
const _codexPreConfigRollback = !hostBehaviorsFor(runtime).tomlConfigInstall || !
|
|
11750
|
+
const _codexPreConfigRollback = !hostBehaviorsFor(runtime).tomlConfigInstall || !_codexPreConfigRollbackArmed ? null : () => {
|
|
11605
11751
|
rollbackInstallerMigrations();
|
|
11606
11752
|
// skills/gsd-* — pass 1: restore snapshot entries (may be absent if deleted mid-install).
|
|
11607
11753
|
const _earlySkillsDir = _resolveSkillsRootDir(runtime, targetDir, _installScopeId);
|
|
@@ -11802,25 +11948,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11802
11948
|
// install (ADR-1239 / #2087), replacing the bespoke inline block this comment
|
|
11803
11949
|
// used to describe. Claude-local remains the one special-cased path
|
|
11804
11950
|
// (copyWithPathReplacement + stale-skills cleanup).
|
|
11805
|
-
const _isSkillsRuntime = (
|
|
11806
|
-
if (hostBehaviorsFor(runtime).localInstallStyle === 'legacy-flat' && !isGlobal) return false; // legacy flat local path (descriptor-driven; #2086)
|
|
11807
|
-
// #2875 Part 2 defect fix: a runtime whose LOCAL commands are embedded in a
|
|
11808
|
-
// rules file rather than materialized as files (hostBehaviors.localCommandsViaRules
|
|
11809
|
-
// — cline is the only declarant, capabilities/cline/capability.json) must not
|
|
11810
|
-
// flip into this skills/commands-reporting branch merely because its local
|
|
11811
|
-
// artifactLayout now also declares an `agents` kind (#2875 Part 2 cline-local
|
|
11812
|
-
// agents regression fix). That branch's own verification reporting expects a
|
|
11813
|
-
// skills/ or commands/ directory this runtime never writes locally and would
|
|
11814
|
-
// spuriously fail; the `localCommandsViaRules` branch below (unchanged
|
|
11815
|
-
// messaging) and the unconditional agents-materialization block further down
|
|
11816
|
-
// (installAgentsKindStandalone) already cover this runtime/scope correctly.
|
|
11817
|
-
if (!isGlobal && hostBehaviorsFor(runtime).localCommandsViaRules) return false;
|
|
11818
|
-
const cap = _capabilityRegistry && _capabilityRegistry.runtimes && _capabilityRegistry.runtimes[runtime];
|
|
11819
|
-
const layout = cap && cap.runtime && cap.runtime.artifactLayout;
|
|
11820
|
-
if (!layout) return false;
|
|
11821
|
-
const scopeLayout = isGlobal ? layout.global : layout.local;
|
|
11822
|
-
return Array.isArray(scopeLayout) && scopeLayout.length > 0;
|
|
11823
|
-
})();
|
|
11951
|
+
const _isSkillsRuntime = isSkillsRuntimeInstall(runtime, _installScopeId);
|
|
11824
11952
|
|
|
11825
11953
|
// Install the distribution-owned gsd-core tree before layout materialization.
|
|
11826
11954
|
// installRuntimeArtifacts then provisions the durable Runtime Surface corpus
|
|
@@ -11889,19 +12017,6 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11889
12017
|
if (_isSkillsRuntime) {
|
|
11890
12018
|
// Layout-driven install for skills-based runtimes (full and minimal modes)
|
|
11891
12019
|
const scope = _installScopeId;
|
|
11892
|
-
// Preserve an existing Runtime Surface selection during upgrades. The
|
|
11893
|
-
// installer refreshes the source corpus first, then emits exactly the
|
|
11894
|
-
// already-committed selection instead of widening it to the base profile.
|
|
11895
|
-
const _committedSurface = readSurface(targetDir);
|
|
11896
|
-
if (_committedSurface) {
|
|
11897
|
-
_resolvedProfile = resolveSurface(
|
|
11898
|
-
targetDir,
|
|
11899
|
-
loadSkillsManifest(_commandsDir),
|
|
11900
|
-
undefined,
|
|
11901
|
-
_installedCapabilityRegistry,
|
|
11902
|
-
_committedSurface,
|
|
11903
|
-
);
|
|
11904
|
-
}
|
|
11905
12020
|
// ADR-1239 upgrade 3 / #2088: a kind may declare an alternate install `home`
|
|
11906
12021
|
// (e.g. Codex skills -> $HOME/.agents/skills) instead of the runtime's normal
|
|
11907
12022
|
// configDir. Resolve the ACTUAL on-disk skills root here, descriptor-driven
|
|
@@ -12310,7 +12425,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12310
12425
|
// returns `null` without writing anything.
|
|
12311
12426
|
if (hostBehaviorsFor(runtime).pluginOnlyInstall) {
|
|
12312
12427
|
console.log(` ${green}✓${reset} pi: no subagent files (programmatic dispatch, no named-dispatch toolkit)`);
|
|
12313
|
-
} else if (_isSkillsRuntime) {
|
|
12428
|
+
} else if (!shouldInstallStandaloneAgents(Boolean(hostBehaviorsFor(runtime).pluginOnlyInstall), _isSkillsRuntime)) {
|
|
12314
12429
|
console.log(` ${dim}↳${reset} Agents installed via descriptor-driven layout (${runtime})`);
|
|
12315
12430
|
} else {
|
|
12316
12431
|
const _standaloneProjectDir = isGlobal ? process.cwd() : targetDir;
|
|
@@ -13298,35 +13413,13 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
13298
13413
|
// failure (#2760 fix 3) — not to the post-agent-merge state.
|
|
13299
13414
|
const preWriteBackup = codexConfigPreInstallSnapshot;
|
|
13300
13415
|
try {
|
|
13301
|
-
|
|
13302
|
-
const
|
|
13303
|
-
|
|
13304
|
-
|
|
13305
|
-
// only touches user-authored hooks, not GSD-owned stale entries. Running
|
|
13306
|
-
// strip after migration causes Shape 1 (legacy gsd-update-check filename)
|
|
13307
|
-
// to be converted by migration before the strip regex can match it (#2698).
|
|
13308
|
-
//
|
|
13309
|
-
// Historical shapes stripped, in order:
|
|
13310
|
-
// Shape 1 — legacy gsd-update-check filename (pre-#1755): flat [[hooks]] + event
|
|
13311
|
-
// Shape 2 — flat [[hooks]] + event = "SessionStart" (#2637 era, never correct)
|
|
13312
|
-
// Shape 4 — correct two-block nested (strip before shape 3 to avoid orphaned header)
|
|
13313
|
-
// Shape 3 — single-block [[hooks.SessionStart]] without nested .hooks (#2760 era)
|
|
13314
|
-
configContent = stripStaleGsdHookBlocks(configContent);
|
|
13315
|
-
|
|
13316
|
-
// Migrate legacy [hooks] map format and flat [[hooks]] AoT entries to the
|
|
13317
|
-
// namespaced [[hooks.<EVENT>]] form after stripping GSD-managed stale blocks.
|
|
13318
|
-
// Running migration after strip ensures only user-authored hooks are migrated
|
|
13319
|
-
// (#2698 regression: migration before strip converts stale GSD blocks before
|
|
13320
|
-
// the strip regexes can match their original shape).
|
|
13321
|
-
const migratedContent = migrateCodexHooksMapFormat(configContent);
|
|
13322
|
-
if (migratedContent !== configContent) {
|
|
13323
|
-
configContent = migratedContent;
|
|
13416
|
+
const existingConfigContent = fs.existsSync(configPath) ? fs.readFileSync(configPath, 'utf-8') : '';
|
|
13417
|
+
const prepared = prepareCodexHooksConfig(existingConfigContent);
|
|
13418
|
+
const configContent = prepared.content;
|
|
13419
|
+
if (prepared.migrated) {
|
|
13324
13420
|
console.log(` ${green}✓${reset} Migrated legacy Codex [hooks] format to two-level nested AoT`);
|
|
13325
13421
|
}
|
|
13326
13422
|
|
|
13327
|
-
const codexHooksFeature = ensureCodexHooksFeature(configContent);
|
|
13328
|
-
configContent = setManagedCodexHooksOwnership(codexHooksFeature.content, codexHooksFeature.ownership);
|
|
13329
|
-
|
|
13330
13423
|
// GSD-managed Codex hook payloads now live in hooks.json to avoid mixed
|
|
13331
13424
|
// representation warnings when a single layer contains both hooks.json
|
|
13332
13425
|
// and inline [hooks] entries. Keep config.toml focused on feature flags
|
|
@@ -13855,14 +13948,18 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
13855
13948
|
// skip managed-hook registration. Returning null from buildHookCommand on
|
|
13856
13949
|
// global installs has the same effect. Better to skip than to emit a bare
|
|
13857
13950
|
// `node` command that recreates the #2979 failure.
|
|
13951
|
+
// #5850: Claude on Windows always gets the PowerShell runner (owner ruling).
|
|
13952
|
+
const localUsesPowerShell = hooksSurface.usesPowerShellHookShell({ platform: process.platform, runtime });
|
|
13858
13953
|
const localCmd = (hookFile) => localNodeRunner === null
|
|
13859
13954
|
? null
|
|
13860
|
-
: hooksSurface.recordConfiguredHookCommand(
|
|
13861
|
-
|
|
13862
|
-
|
|
13863
|
-
|
|
13864
|
-
|
|
13865
|
-
|
|
13955
|
+
: hooksSurface.recordConfiguredHookCommand(localUsesPowerShell
|
|
13956
|
+
? hooksSurface.buildPowerShellLocalHookCommand({ ...hookOpts, localPrefix, hookFile })
|
|
13957
|
+
: projectShellCommandText({
|
|
13958
|
+
runnerToken: localNodeRunner,
|
|
13959
|
+
argTokens: [`${localPrefix}/hooks/${hookFile}`],
|
|
13960
|
+
runtime,
|
|
13961
|
+
platform: process.platform,
|
|
13962
|
+
}), targetDir, hookFile, hookOpts);
|
|
13866
13963
|
const localShellCmd = (hookFile) => hooksSurface.recordConfiguredHookCommand(buildLocalShellHookCommand({
|
|
13867
13964
|
localPrefix,
|
|
13868
13965
|
hookFile,
|
|
@@ -13871,8 +13968,10 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
13871
13968
|
platform: process.platform,
|
|
13872
13969
|
}), targetDir, hookFile, hookOpts);
|
|
13873
13970
|
const statuslineCommand = isGlobal
|
|
13874
|
-
? buildHookCommand(targetDir, 'gsd-statusline.js', hookOpts)
|
|
13875
|
-
:
|
|
13971
|
+
? buildHookCommand(targetDir, 'gsd-statusline.js', { ...hookOpts, statusLine: true })
|
|
13972
|
+
// #5850: a local statusLine has no `$`-free PowerShell spelling (the project
|
|
13973
|
+
// dir is a shell variable); it is skipped on Windows rather than written broken.
|
|
13974
|
+
: (localUsesPowerShell ? null : localCmd('gsd-statusline.js'));
|
|
13876
13975
|
const updateCheckCommand = isGlobal
|
|
13877
13976
|
? buildHookCommand(targetDir, 'gsd-check-update.js', hookOpts)
|
|
13878
13977
|
: localCmd('gsd-check-update.js');
|
|
@@ -13993,7 +14092,9 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
13993
14092
|
const candidate = path.join(probeDir, '.planning', 'config.json');
|
|
13994
14093
|
if (fs.existsSync(candidate)) {
|
|
13995
14094
|
try {
|
|
13996
|
-
|
|
14095
|
+
// #5759: bounded regular-file read — a FIFO/device/oversized config throws to the
|
|
14096
|
+
// catch below (treated as enabled) instead of blocking the installer.
|
|
14097
|
+
const parsed = JSON.parse(stripJsonComments(readConfigFileBounded(candidate) ?? ''));
|
|
13997
14098
|
if (parsed && typeof parsed === 'object' &&
|
|
13998
14099
|
parsed.workflow && parsed.workflow.use_worktrees === false) {
|
|
13999
14100
|
worktreesEnabled = false;
|
|
@@ -14151,7 +14252,7 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
14151
14252
|
// #3002 CR: don't write { type: 'command', command: null } — the
|
|
14152
14253
|
// runtime's settings schema rejects null commands and the failure
|
|
14153
14254
|
// surfaces as a confusing parse error rather than a usable diagnostic.
|
|
14154
|
-
console.warn(` ${yellow}⚠${reset} Skipped statusline registration — Node executable path unavailable (process.execPath is empty). See #2979 / #3002.`);
|
|
14255
|
+
console.warn(` ${yellow}⚠${reset} Skipped statusline registration — Node executable path unavailable (process.execPath is empty), or a local Windows install (no shell-neutral statusline spelling). See #2979 / #3002 / #5850.`);
|
|
14155
14256
|
} else {
|
|
14156
14257
|
settings.statusLine = {
|
|
14157
14258
|
type: 'command',
|
|
@@ -14213,6 +14314,9 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
14213
14314
|
// rejects at parse time. validateHookFields filters those out so the file
|
|
14214
14315
|
// we write is always schema-valid.
|
|
14215
14316
|
if (settingsPath && settings && plan.writesSharedSettings) {
|
|
14317
|
+
// #5850: pin `shell: "powershell"` on PowerShell-form hooks so Claude Code
|
|
14318
|
+
// does not depend on its own Git Bash detection to choose the shell.
|
|
14319
|
+
hooksSurface.projectPowerShellHookShell(settings);
|
|
14216
14320
|
writeSettings(settingsPath, validateHookFields(settings));
|
|
14217
14321
|
}
|
|
14218
14322
|
|