@opengsd/gsd-core 1.18.0 → 1.19.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 +2 -1
- package/.opencode/plugins/gsd-core.js +2 -3
- package/agents/gsd-code-fixer.compact.md +2 -2
- package/agents/gsd-code-fixer.md +4 -3
- package/agents/gsd-verifier.md +1 -1
- package/bin/install.js +422 -103
- package/gsd-core/bin/gsd-tools.cjs +106 -7
- package/gsd-core/bin/lib/agent-install-check.cjs +29 -3
- package/gsd-core/bin/lib/args-terminator.cjs +114 -0
- package/gsd-core/bin/lib/broken-windows.cjs +59 -1
- package/gsd-core/bin/lib/capability-lock.cjs +13 -2
- package/gsd-core/bin/lib/capability-registry.cjs +156 -74
- package/gsd-core/bin/lib/capability-state.cjs +14 -8
- package/gsd-core/bin/lib/capability-validator.cjs +17 -0
- package/gsd-core/bin/lib/config-loader.cjs +10 -0
- package/gsd-core/bin/lib/config.cjs +256 -58
- package/gsd-core/bin/lib/core-utils.cjs +34 -0
- package/gsd-core/bin/lib/decision-coverage-support.cjs +74 -0
- package/gsd-core/bin/lib/gate-context-drift.cjs +8 -3
- package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +6 -1
- package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +6 -1
- package/gsd-core/bin/lib/gate-evaluation-scope.cjs +134 -13
- package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +63 -22
- package/gsd-core/bin/lib/gate-ui-safety.cjs +23 -5
- package/gsd-core/bin/lib/git-info-exclude.cjs +57 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +18 -3
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +4 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +52 -1
- package/gsd-core/bin/lib/host-integration.cjs +13 -6
- package/gsd-core/bin/lib/init.cjs +65 -1
- package/gsd-core/bin/lib/install-engine.cjs +246 -32
- package/gsd-core/bin/lib/install-profiles.cjs +9 -4
- package/gsd-core/bin/lib/installer-migration-report.cjs +2 -0
- package/gsd-core/bin/lib/markdown-line-diff.cjs +147 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +113 -0
- package/gsd-core/bin/lib/milestone-lock.cjs +8 -1
- package/gsd-core/bin/lib/milestone-release-map.cjs +244 -0
- package/gsd-core/bin/lib/milestone-release-tags.cjs +440 -0
- package/gsd-core/bin/lib/milestone.cjs +43 -1
- package/gsd-core/bin/lib/model-catalog.cjs +8 -0
- package/gsd-core/bin/lib/normalize-test-command.cjs +67 -0
- package/gsd-core/bin/lib/phase-id.cjs +21 -4
- package/gsd-core/bin/lib/phase-lifecycle.cjs +37 -4
- package/gsd-core/bin/lib/phase.cjs +115 -26
- package/gsd-core/bin/lib/planning-snapshot.cjs +3 -1
- package/gsd-core/bin/lib/planning-workspace.cjs +18 -0
- package/gsd-core/bin/lib/pristine-baseline.cjs +11 -3
- package/gsd-core/bin/lib/read-only-git-env.cjs +58 -0
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +35 -2
- package/gsd-core/bin/lib/review-lane-runner.cjs +33 -4
- package/gsd-core/bin/lib/roadmap-parser.cjs +12 -0
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +39 -2
- package/gsd-core/bin/lib/roadmap.cjs +33 -25
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +381 -14
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +8 -4
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +60 -16
- package/gsd-core/bin/lib/shell-command-projection.cjs +121 -16
- package/gsd-core/bin/lib/smart-entry.cjs +13 -0
- package/gsd-core/bin/lib/state-contract.cjs +29 -9
- package/gsd-core/bin/lib/state-document.cjs +12 -4
- package/gsd-core/bin/lib/state-transition.cjs +119 -24
- package/gsd-core/bin/lib/state.cjs +50 -28
- package/gsd-core/bin/lib/surface.cjs +9 -0
- package/gsd-core/bin/lib/task-command-router.cjs +5 -25
- package/gsd-core/bin/lib/task-content-resolution.cjs +46 -1
- package/gsd-core/bin/lib/verification.cjs +96 -9
- package/gsd-core/bin/lib/verify.cjs +2 -0
- package/gsd-core/bin/lib/worktree-safety.cjs +22 -1
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
- package/gsd-core/references/git-integration.md +11 -0
- package/gsd-core/references/loop-hook-dispatch.md +13 -6
- package/gsd-core/references/planning-config.md +2 -0
- package/gsd-core/references/tdd.md +2 -2
- package/gsd-core/templates/DEBUG.md +16 -0
- package/gsd-core/templates/config.json +2 -1
- package/gsd-core/templates/verification-report.md +1 -1
- package/gsd-core/workflows/audit-fix.md +2 -1
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +10 -1
- package/gsd-core/workflows/code-review.md +61 -7
- package/gsd-core/workflows/complete-milestone/steps/project-md-evolution-review.md +145 -0
- package/gsd-core/workflows/complete-milestone/steps/release-map.md +80 -0
- package/gsd-core/workflows/complete-milestone.md +37 -150
- package/gsd-core/workflows/diagnose-issues.md +29 -5
- package/gsd-core/workflows/discuss-phase/modes/power.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +33 -2
- package/gsd-core/workflows/discuss-phase-power.md +19 -2
- package/gsd-core/workflows/discuss-phase.md +6 -10
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +13 -7
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +7 -2
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -1
- package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +11 -2
- package/gsd-core/workflows/execute-phase.md +9 -5
- package/gsd-core/workflows/health.md +2 -1
- package/gsd-core/workflows/import.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +2 -2
- package/gsd-core/workflows/plan-phase.md +13 -13
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +1 -1
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +8 -1
- package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +2 -1
- package/gsd-core/workflows/quick.md +1 -1
- package/gsd-core/workflows/review.md +16 -13
- package/gsd-core/workflows/section-manifest.json +5 -0
- package/gsd-core/workflows/settings.md +1 -0
- package/gsd-core/workflows/ship.md +3 -4
- package/gsd-core/workflows/spec-phase.md +3 -6
- package/gsd-core/workflows/verify-work.md +4 -3
- package/hooks/dist/gsd-agent-isolation-guard.js +11 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +6 -2
- package/hooks/dist/gsd-path-export.js +95 -0
- package/hooks/dist/gsd-secret-read-guard.js +172 -39
- package/hooks/dist/gsd-statusline.js +35 -2
- package/hooks/dist/gsd-windsurf-pre-write.js +28 -1
- package/hooks/dist/gsd-workflow-guard.js +1 -1
- package/hooks/dist/gsd-worktree-path-guard.js +28 -1
- package/hooks/dist/lib/isolation-sentinel.js +44 -0
- package/hooks/dist/managed-hooks-registry.cjs +1 -0
- package/hooks/gsd-agent-isolation-guard.js +11 -1
- package/hooks/gsd-cursor-subagent-start.js +6 -2
- package/hooks/gsd-path-export.js +95 -0
- package/hooks/gsd-secret-read-guard.js +172 -39
- package/hooks/gsd-statusline.js +35 -2
- package/hooks/gsd-windsurf-pre-write.js +28 -1
- package/hooks/gsd-workflow-guard.js +1 -1
- package/hooks/gsd-worktree-path-guard.js +28 -1
- package/hooks/hooks.json +1 -0
- package/hooks/lib/isolation-sentinel.js +44 -0
- package/hooks/managed-hooks-registry.cjs +1 -0
- package/package.json +13 -6
- package/pi/gsd.cjs +1047 -10
- package/pi/package-agents/gsd-advisor-researcher.md +113 -0
- package/pi/package-agents/gsd-ai-researcher.md +111 -0
- package/pi/package-agents/gsd-assumptions-analyzer.md +110 -0
- package/pi/package-agents/gsd-code-fixer.md +769 -0
- package/pi/package-agents/gsd-code-reviewer.md +402 -0
- package/pi/package-agents/gsd-codebase-mapper.md +850 -0
- package/pi/package-agents/gsd-debug-session-manager.md +416 -0
- package/pi/package-agents/gsd-debugger.md +1274 -0
- package/pi/package-agents/gsd-doc-classifier.md +269 -0
- package/pi/package-agents/gsd-doc-synthesizer.md +259 -0
- package/pi/package-agents/gsd-doc-verifier.md +212 -0
- package/pi/package-agents/gsd-doc-writer.md +626 -0
- package/pi/package-agents/gsd-dom-verifier.md +164 -0
- package/pi/package-agents/gsd-domain-researcher.md +144 -0
- package/pi/package-agents/gsd-eval-auditor.md +185 -0
- package/pi/package-agents/gsd-eval-planner.md +149 -0
- package/pi/package-agents/gsd-executor.md +894 -0
- package/pi/package-agents/gsd-framework-selector.md +159 -0
- package/pi/package-agents/gsd-integration-checker.md +476 -0
- package/pi/package-agents/gsd-intel-updater.md +338 -0
- package/pi/package-agents/gsd-mempalace-curator.md +49 -0
- package/pi/package-agents/gsd-nyquist-auditor.md +200 -0
- package/pi/package-agents/gsd-pattern-mapper.md +340 -0
- package/pi/package-agents/gsd-phase-researcher.md +897 -0
- package/pi/package-agents/gsd-plan-checker.md +1077 -0
- package/pi/package-agents/gsd-planner.md +1003 -0
- package/pi/package-agents/gsd-project-researcher.md +611 -0
- package/pi/package-agents/gsd-research-synthesizer.md +259 -0
- package/pi/package-agents/gsd-roadmapper.md +779 -0
- package/pi/package-agents/gsd-security-auditor.md +171 -0
- package/pi/package-agents/gsd-ui-auditor.md +581 -0
- package/pi/package-agents/gsd-ui-checker.md +417 -0
- package/pi/package-agents/gsd-ui-researcher.md +469 -0
- package/pi/package-agents/gsd-user-profiler.md +174 -0
- package/pi/package-agents/gsd-verifier.md +963 -0
- package/pi/package-agents/manifest.json +39 -0
- package/pi/package-skills/gsd-add-tests/SKILL.md +39 -0
- package/pi/package-skills/gsd-ai-integration-phase/SKILL.md +36 -0
- package/pi/package-skills/gsd-audit-fix/SKILL.md +34 -0
- package/pi/package-skills/gsd-audit-milestone/SKILL.md +40 -0
- package/pi/package-skills/gsd-audit-uat/SKILL.md +26 -0
- package/pi/package-skills/gsd-autonomous/SKILL.md +52 -0
- package/pi/package-skills/gsd-capture/SKILL.md +68 -0
- package/pi/package-skills/gsd-cleanup/SKILL.md +25 -0
- package/pi/package-skills/gsd-code-review/SKILL.md +71 -0
- package/pi/package-skills/gsd-complete-milestone/SKILL.md +147 -0
- package/pi/package-skills/gsd-config/SKILL.md +60 -0
- package/pi/package-skills/gsd-debug/SKILL.md +64 -0
- package/pi/package-skills/gsd-discuss-phase/SKILL.md +74 -0
- package/pi/package-skills/gsd-docs-update/SKILL.md +50 -0
- package/pi/package-skills/gsd-eval-review/SKILL.md +35 -0
- package/pi/package-skills/gsd-execute-phase/SKILL.md +62 -0
- package/pi/package-skills/gsd-explore/SKILL.md +26 -0
- package/pi/package-skills/gsd-extract-learnings/SKILL.md +25 -0
- package/pi/package-skills/gsd-fast/SKILL.md +33 -0
- package/pi/package-skills/gsd-forensics/SKILL.md +59 -0
- package/pi/package-skills/gsd-graphify/SKILL.md +210 -0
- package/pi/package-skills/gsd-health/SKILL.md +36 -0
- package/pi/package-skills/gsd-help/SKILL.md +37 -0
- package/pi/package-skills/gsd-import/SKILL.md +46 -0
- package/pi/package-skills/gsd-inbox/SKILL.md +42 -0
- package/pi/package-skills/gsd-ingest-docs/SKILL.md +43 -0
- package/pi/package-skills/gsd-manager/SKILL.md +46 -0
- package/pi/package-skills/gsd-map-codebase/SKILL.md +85 -0
- package/pi/package-skills/gsd-mempalace-capture/SKILL.md +111 -0
- package/pi/package-skills/gsd-mempalace-recall/SKILL.md +111 -0
- package/pi/package-skills/gsd-milestone-summary/SKILL.md +54 -0
- package/pi/package-skills/gsd-mvp-phase/SKILL.md +45 -0
- package/pi/package-skills/gsd-new-milestone/SKILL.md +49 -0
- package/pi/package-skills/gsd-new-project/SKILL.md +49 -0
- package/pi/package-skills/gsd-next/SKILL.md +34 -0
- package/pi/package-skills/gsd-ns-context/SKILL.md +31 -0
- package/pi/package-skills/gsd-ns-ideate/SKILL.md +30 -0
- package/pi/package-skills/gsd-ns-manage/SKILL.md +42 -0
- package/pi/package-skills/gsd-ns-project/SKILL.md +34 -0
- package/pi/package-skills/gsd-ns-review/SKILL.md +35 -0
- package/pi/package-skills/gsd-ns-workflow/SKILL.md +44 -0
- package/pi/package-skills/gsd-onboard/SKILL.md +46 -0
- package/pi/package-skills/gsd-pause-work/SKILL.md +48 -0
- package/pi/package-skills/gsd-phase/SKILL.md +61 -0
- package/pi/package-skills/gsd-plan-phase/SKILL.md +62 -0
- package/pi/package-skills/gsd-plan-review-convergence/SKILL.md +63 -0
- package/pi/package-skills/gsd-pr-branch/SKILL.md +31 -0
- package/pi/package-skills/gsd-profile-user/SKILL.md +48 -0
- package/pi/package-skills/gsd-progress/SKILL.md +51 -0
- package/pi/package-skills/gsd-quick/SKILL.md +179 -0
- package/pi/package-skills/gsd-quick-batch/SKILL.md +114 -0
- package/pi/package-skills/gsd-resume-work/SKILL.md +31 -0
- package/pi/package-skills/gsd-review/SKILL.md +50 -0
- package/pi/package-skills/gsd-review-backlog/SKILL.md +65 -0
- package/pi/package-skills/gsd-secure-phase/SKILL.md +37 -0
- package/pi/package-skills/gsd-settings/SKILL.md +30 -0
- package/pi/package-skills/gsd-ship/SKILL.md +27 -0
- package/pi/package-skills/gsd-sketch/SKILL.md +56 -0
- package/pi/package-skills/gsd-spec-phase/SKILL.md +63 -0
- package/pi/package-skills/gsd-spike/SKILL.md +53 -0
- package/pi/package-skills/gsd-stats/SKILL.md +22 -0
- package/pi/package-skills/gsd-surface/SKILL.md +177 -0
- package/pi/package-skills/gsd-thread/SKILL.md +29 -0
- package/pi/package-skills/gsd-ui-phase/SKILL.md +35 -0
- package/pi/package-skills/gsd-ui-review/SKILL.md +35 -0
- package/pi/package-skills/gsd-ultraplan-phase/SKILL.md +38 -0
- package/pi/package-skills/gsd-undo/SKILL.md +38 -0
- package/pi/package-skills/gsd-update/SKILL.md +51 -0
- package/pi/package-skills/gsd-validate-phase/SKILL.md +37 -0
- package/pi/package-skills/gsd-verify-work/SKILL.md +41 -0
- package/pi/package-skills/gsd-workspace/SKILL.md +57 -0
- package/pi/package-skills/gsd-workstreams/SKILL.md +73 -0
- package/scripts/build-hooks.js +4 -0
- package/scripts/command-contract-helpers.cjs +1 -1
- package/scripts/docs-guard-registry.cjs +20 -7
- package/scripts/gen-health-docs.cjs +13 -1
- package/scripts/gen-pi-package-skills.cjs +217 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +11 -1
- package/scripts/lib/platform-conformance-tier.generated.cjs +26 -1
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +3 -3
- package/scripts/lint-retired-runtime-name.cjs +5 -0
- package/scripts/npm-audit-baseline.cjs +41 -7
- package/scripts/reviewed-or-maintainer-policy.cjs +181 -0
- package/scripts/select-docs-guards.cjs +1 -1
- package/scripts/self-test-report-policy.cjs +350 -0
- package/vscode/package.json +1 -1
- package/scripts/diagnose-validate-commit-in-tier.cjs +0 -270
- package/scripts/diagnose-validate-commit-phases.cjs +0 -348
- package/scripts/xtrace-phase-timing.cjs +0 -205
|
@@ -52,6 +52,7 @@ const userArtifactStaging = require("./user-artifact-staging.cjs");
|
|
|
52
52
|
// module-level doc comment on `isGlobalScope` for why the projection is
|
|
53
53
|
// centralized rather than eliminated).
|
|
54
54
|
const install_scope_cjs_1 = require("./install-scope.cjs");
|
|
55
|
+
const args_terminator_cjs_1 = require("./args-terminator.cjs");
|
|
55
56
|
const { processAttribution } = runtimeArtifactConversion;
|
|
56
57
|
// resolveRuntimeArtifactLayout: accessed via module ref (not destructured) so
|
|
57
58
|
// test stubs that monkeypatch the module's exports are seen at call time.
|
|
@@ -233,7 +234,8 @@ function applyOpencodeFamilyPathPrefix(content, runtime, pathPrefix) {
|
|
|
233
234
|
content = content.replace(/\.\/\.claude\//g, `./${getDirName(runtime)}/`);
|
|
234
235
|
content = content.replace(/~\/\.opencode\//g, pathPrefix);
|
|
235
236
|
content = content.replace(/~\/\.kilo\//g, pathPrefix);
|
|
236
|
-
|
|
237
|
+
// #5637: the typed-text capture heredoc ends at a terminator the typed text cannot know.
|
|
238
|
+
return (0, args_terminator_cjs_1.stampInstalledContent)(content);
|
|
237
239
|
}
|
|
238
240
|
/**
|
|
239
241
|
* Convert a Claude command (.md) to an OpenCode skill (SKILL.md).
|
|
@@ -755,10 +757,17 @@ function _copyStaged(stagedDir, destDir, kind, configDir, runtime) {
|
|
|
755
757
|
* For the prefix='' case: the destSubpath IS the namespace — remove the entire
|
|
756
758
|
* destDir. (No current runtime uses prefix='' after #947 reversed Hermes; kept
|
|
757
759
|
* as a defensive guard for future runtimes.)
|
|
760
|
+
*
|
|
761
|
+
* `owns` (#5161): when supplied, a prefix match is necessary but NOT
|
|
762
|
+
* sufficient — an entry is removed only when `owns(name)` is true, and every
|
|
763
|
+
* prefix-matching entry it rejects is returned so the caller can name it.
|
|
764
|
+
* Omitted (agents, commands, uninstall), every prefix match is removed as
|
|
765
|
+
* before and the return value is always empty.
|
|
758
766
|
*/
|
|
759
|
-
function _removeGsdEntries(destDir, kind) {
|
|
767
|
+
function _removeGsdEntries(destDir, kind, owns) {
|
|
768
|
+
const preserved = [];
|
|
760
769
|
if (!installFs().existsSync(destDir))
|
|
761
|
-
return;
|
|
770
|
+
return preserved;
|
|
762
771
|
if (kind.kind === 'kimi-agents') {
|
|
763
772
|
for (const fileName of ['gsd.yaml', 'gsd.md']) {
|
|
764
773
|
installFs().rmSync(node_path_1.default.join(destDir, fileName), { force: true });
|
|
@@ -775,19 +784,158 @@ function _removeGsdEntries(destDir, kind) {
|
|
|
775
784
|
installFs().rmSync(node_path_1.default.join(subagentsDir, entry.name), { force: true });
|
|
776
785
|
}
|
|
777
786
|
}
|
|
778
|
-
return;
|
|
787
|
+
return preserved;
|
|
779
788
|
}
|
|
780
789
|
if (kind.prefix === '') {
|
|
781
790
|
// Whole-namespace removal (Hermes nested case — destSubpath is skills/gsd)
|
|
782
791
|
// The directory itself is the GSD namespace, so remove it entirely.
|
|
783
792
|
installFs().rmSync(destDir, { recursive: true, force: true });
|
|
784
|
-
return;
|
|
793
|
+
return preserved;
|
|
785
794
|
}
|
|
786
795
|
for (const entry of installFs().readdirSync(destDir, { withFileTypes: true })) {
|
|
787
796
|
if (!entry.name.startsWith(kind.prefix))
|
|
788
797
|
continue;
|
|
798
|
+
if (owns && !owns(entry.name)) {
|
|
799
|
+
preserved.push(entry.name);
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
789
802
|
installFs().rmSync(node_path_1.default.join(destDir, entry.name), { recursive: true, force: true });
|
|
790
803
|
}
|
|
804
|
+
return preserved;
|
|
805
|
+
}
|
|
806
|
+
// ---------------------------------------------------------------------------
|
|
807
|
+
// Skill-dir ownership (#5161)
|
|
808
|
+
// ---------------------------------------------------------------------------
|
|
809
|
+
/**
|
|
810
|
+
* GSD-prefixed skill dirs the user owns and GSD never reinstalls from source
|
|
811
|
+
* (#2973). The skills writers snapshot them across the prune and restore them.
|
|
812
|
+
*/
|
|
813
|
+
const USER_OWNED_SKILL_DIRS = ['gsd-dev-preferences'];
|
|
814
|
+
/** The skill dirs each writer wrote, keyed on the resolved skills dir (recordWrittenSkillDirs). */
|
|
815
|
+
const writtenSkillDirs = new Map();
|
|
816
|
+
/**
|
|
817
|
+
* Record the skill dirs a writer just wrote into `skillsDir`, replacing that
|
|
818
|
+
* dir's entry. Called only once the write has returned, so a writer that threw
|
|
819
|
+
* records nothing.
|
|
820
|
+
*/
|
|
821
|
+
function recordWrittenSkillDirs(skillsDir, names) {
|
|
822
|
+
writtenSkillDirs.set(node_path_1.default.resolve(skillsDir), new Set(names));
|
|
823
|
+
}
|
|
824
|
+
/** Start an install's record empty, so no earlier install's writes are read. */
|
|
825
|
+
function clearWrittenSkillDirs() {
|
|
826
|
+
writtenSkillDirs.clear();
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Build the predicate that decides whether a prefix-matching dir in
|
|
830
|
+
* `skillsDir` is GSD-owned (#5161). A prefix match is necessary but NOT
|
|
831
|
+
* sufficient — the rule surface.cts pruneSkillDirs already applies. A dir is
|
|
832
|
+
* GSD-owned when any of these hold:
|
|
833
|
+
* - it is a first-party skill: `<prefix><stem>` for a commands/gsd/<stem>.md
|
|
834
|
+
* in the install source;
|
|
835
|
+
* - it carries the capability-skill marker (a third-party capability skill
|
|
836
|
+
* GSD staged, install-profiles.cts CAPABILITY_SKILL_MARKER);
|
|
837
|
+
* - `includeManifest` is not false and the previous install's
|
|
838
|
+
* gsd-file-manifest.json records a file under it (a skill GSD installed
|
|
839
|
+
* before that has since been retired or left the active profile).
|
|
840
|
+
* Anything else is user-owned or unknown. The first-time baseline migration
|
|
841
|
+
* may already have resolved it to `keep`, and the prune must honor that.
|
|
842
|
+
*
|
|
843
|
+
* writeManifest builds this with `includeManifest: false`: a preserved user
|
|
844
|
+
* dir recorded into the manifest would read back as GSD-owned on the next
|
|
845
|
+
* install and be deleted then.
|
|
846
|
+
*
|
|
847
|
+
* `manifestIfSourceUnresolved` is writeManifest's answer when the install
|
|
848
|
+
* source cannot be read, which leaves no first-party leg. It turns the manifest
|
|
849
|
+
* leg back on for that case only, so the dirs the previous install recorded
|
|
850
|
+
* stay recorded and the fallback adds no dir the previous manifest does not
|
|
851
|
+
* already record (beyond capability-marked ones). Owning every dir instead
|
|
852
|
+
* would record a user's kept dir, and the next install would read it back as
|
|
853
|
+
* GSD-owned and delete it: the #5161 failure, reached through a fallback. The
|
|
854
|
+
* cost is a skill first shipped by this release going unrecorded until the
|
|
855
|
+
* next install with a readable source. A prune never needs the flag: it keeps
|
|
856
|
+
* every dir it cannot otherwise prove, and its manifest leg is already on.
|
|
857
|
+
*
|
|
858
|
+
* `includeWritten` adds the skill dirs this install wrote into `skillsDir`
|
|
859
|
+
* (recordWrittenSkillDirs). The first-party lookups can miss the corpus a run
|
|
860
|
+
* staged: staging resolves its provider under the layout's requirement and the
|
|
861
|
+
* installed corpus's manifest, so it can take a corpus a `.gsd-source` marker
|
|
862
|
+
* names that neither lookup reaches, and its skills would then go uncounted
|
|
863
|
+
* and unrecorded. Only the skill counts and writeManifest pass the flag, since
|
|
864
|
+
* they must own what the install wrote. Every prune leaves it unset, so the
|
|
865
|
+
* record never widens what is deleted.
|
|
866
|
+
*/
|
|
867
|
+
function createSkillDirOwnership(runtime, configDir, skillsDir, prefix, opts = {}) {
|
|
868
|
+
// First-party stems come from BOTH the configDir-resolved source and the
|
|
869
|
+
// executing package's own source. Staging resolves its provider with the
|
|
870
|
+
// commands+agents requirement while a commands-only lookup can accept a
|
|
871
|
+
// different provider (a commands-only `.gsd-source` marker), so either root
|
|
872
|
+
// alone can miss the corpus the run staged. Both are GSD corpora, so the
|
|
873
|
+
// union only ever widens what GSD owns.
|
|
874
|
+
const firstParty = new Set();
|
|
875
|
+
let sourceResolved = false;
|
|
876
|
+
let lastError = null;
|
|
877
|
+
const roots = [
|
|
878
|
+
() => runtimeArtifactLayout.findInstallSourceRoot(configDir),
|
|
879
|
+
() => runtimeArtifactLayout.findInstallSourceRoot(undefined),
|
|
880
|
+
];
|
|
881
|
+
for (const root of roots) {
|
|
882
|
+
try {
|
|
883
|
+
const commandsGsdDir = root();
|
|
884
|
+
// A regular `.md` file only — the filter every skills writer stages
|
|
885
|
+
// with. A source counts as resolved only once it yields a stem.
|
|
886
|
+
for (const entry of installFs().readdirSync(commandsGsdDir, { withFileTypes: true })) {
|
|
887
|
+
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
888
|
+
continue;
|
|
889
|
+
firstParty.add(prefix + entry.name.slice(0, -3));
|
|
890
|
+
sourceResolved = true;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
catch (err) {
|
|
894
|
+
lastError = err;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
if (!sourceResolved) {
|
|
898
|
+
console.warn(` [gsd] could not read the install source to classify ${skillsDir} (${lastError?.message}) — ${opts.manifestIfSourceUnresolved ? 'treating as GSD-owned only the gsd- skill dirs the previous install manifest records or that carry the capability-skill marker' : 'keeping every gsd- skill dir GSD cannot otherwise prove it owns'}.`);
|
|
899
|
+
}
|
|
900
|
+
const manifestNames = new Set();
|
|
901
|
+
if (opts.includeManifest !== false || (!sourceResolved && opts.manifestIfSourceUnresolved === true)) {
|
|
902
|
+
// The prefix must describe the root being pruned: a manifest name under one
|
|
903
|
+
// root says nothing about a same-named dir under another (Hermes' flat
|
|
904
|
+
// pre-#2841 root vs its nested skills/gsd/ root).
|
|
905
|
+
const manifestPrefix = opts.manifestPrefix ?? (hostBehaviorsFor(runtime).skillsManifestPrefix || 'skills/');
|
|
906
|
+
for (const key of Object.keys(installerMigrations.readInstallManifest(configDir).files)) {
|
|
907
|
+
if (!key.startsWith(manifestPrefix))
|
|
908
|
+
continue;
|
|
909
|
+
const rest = key.slice(manifestPrefix.length);
|
|
910
|
+
const slash = rest.indexOf('/');
|
|
911
|
+
if (slash > 0)
|
|
912
|
+
manifestNames.add(rest.slice(0, slash));
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
const written = opts.includeWritten === true ? writtenSkillDirs.get(node_path_1.default.resolve(skillsDir)) : undefined;
|
|
916
|
+
return (name) => firstParty.has(name) ||
|
|
917
|
+
manifestNames.has(name) ||
|
|
918
|
+
written?.has(name) === true ||
|
|
919
|
+
installFs().existsSync(node_path_1.default.join(skillsDir, name, installProfiles.CAPABILITY_SKILL_MARKER));
|
|
920
|
+
}
|
|
921
|
+
/**
|
|
922
|
+
* Report each prefix-matching skill dir the prune preserved as not GSD-owned,
|
|
923
|
+
* by path (#5161). The deletion this replaces was silent.
|
|
924
|
+
*
|
|
925
|
+
* A USER_OWNED_SKILL_DIRS entry is never named: GSD special-cases it (the
|
|
926
|
+
* writers restore it and writeManifest records it), so "GSD does not own it,
|
|
927
|
+
* rename it" would be false. It reaches this list whenever no previous
|
|
928
|
+
* manifest records it yet, e.g. the first update after the user generates it.
|
|
929
|
+
*
|
|
930
|
+
* The message names no manifest: the legacy-location cleanups decide without
|
|
931
|
+
* one, so "not in the previous install manifest" would be false there.
|
|
932
|
+
*/
|
|
933
|
+
function _warnPreservedSkillDirs(skillsDir, names) {
|
|
934
|
+
for (const name of names) {
|
|
935
|
+
if (USER_OWNED_SKILL_DIRS.includes(name))
|
|
936
|
+
continue;
|
|
937
|
+
console.warn(` [gsd] Preserved ${node_path_1.default.join(skillsDir, name)} — it matches the gsd- skill prefix but nothing shows GSD owns it here. Rename it if it should not share GSD's prefix.`);
|
|
938
|
+
}
|
|
791
939
|
}
|
|
792
940
|
// ---------------------------------------------------------------------------
|
|
793
941
|
// _snapshotDir / _restoreDir
|
|
@@ -833,14 +981,17 @@ function _restoreDir(dir, snapshot) {
|
|
|
833
981
|
*
|
|
834
982
|
* @param nestedGsdDir absolute path to skills/gsd/ category dir
|
|
835
983
|
*/
|
|
836
|
-
function _removeHermesBareStemDirs(nestedGsdDir) {
|
|
984
|
+
function _removeHermesBareStemDirs(nestedGsdDir, owns) {
|
|
837
985
|
if (!installFs().existsSync(nestedGsdDir))
|
|
838
986
|
return;
|
|
839
987
|
const entries = installFs().readdirSync(nestedGsdDir, { withFileTypes: true });
|
|
840
988
|
// Collect the set of stems that were installed as gsd-<stem>/ this run.
|
|
989
|
+
// #5161: the prune now keeps gsd-* dirs GSD does not own, so "present" no
|
|
990
|
+
// longer means "installed". With `owns`, only an owned gsd-<stem>/ counts,
|
|
991
|
+
// and a user's own <stem>/ beside their own gsd-<stem>/ is left alone.
|
|
841
992
|
const installedStems = new Set();
|
|
842
993
|
for (const entry of entries) {
|
|
843
|
-
if (entry.isDirectory() && entry.name.startsWith('gsd-')) {
|
|
994
|
+
if (entry.isDirectory() && entry.name.startsWith('gsd-') && (!owns || owns(entry.name))) {
|
|
844
995
|
installedStems.add(entry.name.slice('gsd-'.length)); // e.g. 'quick', 'dev-preferences'
|
|
845
996
|
}
|
|
846
997
|
}
|
|
@@ -893,15 +1044,37 @@ function _runLegacyInstallMigrations(runtime, configDir, scope = 'global') {
|
|
|
893
1044
|
}
|
|
894
1045
|
}
|
|
895
1046
|
// Hermes: remove pre-#2841 flat skills/gsd-*/ entries that lived alongside
|
|
896
|
-
// the new skills/gsd/ nested layout
|
|
1047
|
+
// the new skills/gsd/ nested layout (#5169: `hostBehaviors.legacyFlatSkillsCleanup`).
|
|
1048
|
+
// #5161: skills/ is also Hermes' own skills root, so this runs on every
|
|
1049
|
+
// install against dirs the user may own — remove only what GSD owns, keep
|
|
1050
|
+
// and name the rest.
|
|
897
1051
|
if (hostBehaviorsFor(runtime).legacyFlatSkillsCleanup) {
|
|
898
1052
|
const flatSkillsDir = node_path_1.default.join(configDir, 'skills');
|
|
899
1053
|
if (installFs().existsSync(flatSkillsDir)) {
|
|
1054
|
+
// The flat root's manifest entries were recorded as skills/gsd-<x>/…, not
|
|
1055
|
+
// under Hermes' current nested prefix.
|
|
1056
|
+
const ownsSkillDir = createSkillDirOwnership(runtime, configDir, flatSkillsDir, 'gsd-', { manifestPrefix: 'skills/' });
|
|
1057
|
+
const kept = [];
|
|
900
1058
|
for (const entry of installFs().readdirSync(flatSkillsDir, { withFileTypes: true })) {
|
|
901
|
-
if (entry.
|
|
902
|
-
|
|
1059
|
+
if (!entry.name.startsWith('gsd-'))
|
|
1060
|
+
continue;
|
|
1061
|
+
// A symlink is never removed here (as before); name it when unowned.
|
|
1062
|
+
if (entry.isSymbolicLink()) {
|
|
1063
|
+
if (!ownsSkillDir(entry.name))
|
|
1064
|
+
kept.push(entry.name);
|
|
1065
|
+
continue;
|
|
903
1066
|
}
|
|
1067
|
+
if (!entry.isDirectory())
|
|
1068
|
+
continue;
|
|
1069
|
+
// gsd-dev-preferences is user content even when an old manifest records
|
|
1070
|
+
// it, and nothing here restores it (#2973), so it is never removed.
|
|
1071
|
+
if (!ownsSkillDir(entry.name) || USER_OWNED_SKILL_DIRS.includes(entry.name)) {
|
|
1072
|
+
kept.push(entry.name);
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
installFs().rmSync(node_path_1.default.join(flatSkillsDir, entry.name), { recursive: true });
|
|
904
1076
|
}
|
|
1077
|
+
_warnPreservedSkillDirs(flatSkillsDir, kept);
|
|
905
1078
|
}
|
|
906
1079
|
// Hermes: bare-stem skills/gsd/<stem>/ cleanup is deferred to AFTER the
|
|
907
1080
|
// layout-driven install loop in installRuntimeArtifacts, where the exact set
|
|
@@ -1117,6 +1290,8 @@ function _runLegacyUninstallCleanup(runtime, configDir, scope = 'global') {
|
|
|
1117
1290
|
*/
|
|
1118
1291
|
function installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile, resolveAttribution = () => undefined, capabilityRegistry, deps = {}) {
|
|
1119
1292
|
return withInstallFs(deps.fs, () => {
|
|
1293
|
+
// #5161: this install's record of the skill dirs it writes starts empty.
|
|
1294
|
+
clearWrittenSkillDirs();
|
|
1120
1295
|
const layout = runtimeArtifactLayout.resolveRuntimeArtifactLayout(runtime, configDir, scope, capabilityRegistry);
|
|
1121
1296
|
// A removed descriptor kind is no longer visited by the layout loop, so it
|
|
1122
1297
|
// cannot prune its own previous output. Clean manifest-proven retired files
|
|
@@ -1253,7 +1428,9 @@ function installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile, res
|
|
|
1253
1428
|
}
|
|
1254
1429
|
installFs().mkdirSync(dest, { recursive: true });
|
|
1255
1430
|
const preserved = [];
|
|
1256
|
-
|
|
1431
|
+
// #5161: keyed on the kind alone, so the non-skills branch below can
|
|
1432
|
+
// never reach a skills dest with the prune that ignores ownership.
|
|
1433
|
+
if (kind.kind === 'skills') {
|
|
1257
1434
|
// Pre-prune: snapshot user-owned content before _removeGsdEntries wipes it,
|
|
1258
1435
|
// then restore after. This preserves user dirs across a wipe-and-replace
|
|
1259
1436
|
// install (#2973 / #3664).
|
|
@@ -1264,24 +1441,37 @@ function installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile, res
|
|
|
1264
1441
|
// gsd-dev-preferences, which GSD does not reinstall from source but must
|
|
1265
1442
|
// survive the prune (#2973).
|
|
1266
1443
|
const toPreserve = new Map(); // dirName -> Map<relPath, Buffer>
|
|
1267
|
-
{
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
continue;
|
|
1275
|
-
const snap = _snapshotDir(skillDir);
|
|
1276
|
-
if (snap.size > 0)
|
|
1277
|
-
toPreserve.set(dirName, snap);
|
|
1278
|
-
}
|
|
1444
|
+
for (const dirName of USER_OWNED_SKILL_DIRS) {
|
|
1445
|
+
const skillDir = node_path_1.default.join(dest, dirName);
|
|
1446
|
+
if (!installFs().existsSync(skillDir))
|
|
1447
|
+
continue;
|
|
1448
|
+
const snap = _snapshotDir(skillDir);
|
|
1449
|
+
if (snap.size > 0)
|
|
1450
|
+
toPreserve.set(dirName, snap);
|
|
1279
1451
|
}
|
|
1280
|
-
|
|
1452
|
+
// #5161: prune only what GSD owns — a dir this run is about to write,
|
|
1453
|
+
// or one createSkillDirOwnership proves GSD's. Any other gsd-* dir is
|
|
1454
|
+
// user-owned or unknown: keep it and name it.
|
|
1455
|
+
const stagedNames = new Set(installFs().existsSync(item.sourceDir) ? installFs().readdirSync(item.sourceDir) : []);
|
|
1456
|
+
const ownsSkillDir = createSkillDirOwnership(runtime, configDir, dest, kind.prefix);
|
|
1457
|
+
// A USER_OWNED_SKILL_DIRS entry is never pruned, even when the manifest
|
|
1458
|
+
// leg owns it: the restore below skips symlinks, so a prune-then-restore
|
|
1459
|
+
// loses a symlinked SKILL.md. The restore remains for prune paths that
|
|
1460
|
+
// do not consult `owns`.
|
|
1461
|
+
const notOwned = _removeGsdEntries(dest, kind, (name) => !USER_OWNED_SKILL_DIRS.includes(name) && (stagedNames.has(name) || ownsSkillDir(name)));
|
|
1462
|
+
_warnPreservedSkillDirs(dest, notOwned);
|
|
1281
1463
|
_copyStaged(item.sourceDir, dest, kind, configDir, runtime);
|
|
1282
|
-
//
|
|
1464
|
+
// The dirs _copyStaged just wrote: each directory child of the stage.
|
|
1465
|
+
recordWrittenSkillDirs(dest, installFs().existsSync(item.sourceDir)
|
|
1466
|
+
? installFs().readdirSync(item.sourceDir, { withFileTypes: true })
|
|
1467
|
+
.filter((e) => e.isDirectory()).map((e) => e.name)
|
|
1468
|
+
: []);
|
|
1469
|
+
// Restore user-owned dirs after the prune+copy — only one a prune
|
|
1470
|
+
// branch that does not consult `owns` actually removed. Writing over
|
|
1471
|
+
// the kept dir would fail on a read-only file the user left there.
|
|
1283
1472
|
for (const [dirName, snap] of toPreserve) {
|
|
1284
|
-
|
|
1473
|
+
if (!installFs().existsSync(node_path_1.default.join(dest, dirName)))
|
|
1474
|
+
_restoreDir(node_path_1.default.join(dest, dirName), snap);
|
|
1285
1475
|
preserved.push(dirName);
|
|
1286
1476
|
}
|
|
1287
1477
|
}
|
|
@@ -1322,7 +1512,10 @@ function installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile, res
|
|
|
1322
1512
|
let hermesBareStemCleanup = false;
|
|
1323
1513
|
if (hostBehaviorsFor(runtime).bareStemSkillsCleanup) {
|
|
1324
1514
|
const nestedGsdDirForCleanup = node_path_1.default.join(configDir, 'skills', 'gsd');
|
|
1325
|
-
|
|
1515
|
+
// gsd-dev-preferences is restored rather than staged, and its legacy bare
|
|
1516
|
+
// dev-preferences/ twin is one this cleanup exists for (#947).
|
|
1517
|
+
const ownsNested = createSkillDirOwnership(runtime, configDir, nestedGsdDirForCleanup, 'gsd-', { includeManifest: false });
|
|
1518
|
+
_removeHermesBareStemDirs(nestedGsdDirForCleanup, (name) => ownsNested(name) || USER_OWNED_SKILL_DIRS.includes(name));
|
|
1326
1519
|
hermesBareStemCleanup = true;
|
|
1327
1520
|
}
|
|
1328
1521
|
// Generic-branch nativePlugin staging (ADR-1239 / #2102 Stage 1): runtimes
|
|
@@ -1427,7 +1620,6 @@ function installOpencodeFamilySkills(runtime, targetDir, rawCommandsDir, pathPre
|
|
|
1427
1620
|
// and lives at <configDir>/skills/gsd-dev-preferences — _removeGsdEntries
|
|
1428
1621
|
// would otherwise wipe it. Mirrors the preservation in installRuntimeArtifacts
|
|
1429
1622
|
// (#2973).
|
|
1430
|
-
const USER_OWNED_SKILL_DIRS = ['gsd-dev-preferences'];
|
|
1431
1623
|
const toPreserve = new Map(); // dirName -> Map<relPath, Buffer>
|
|
1432
1624
|
for (const dirName of USER_OWNED_SKILL_DIRS) {
|
|
1433
1625
|
const skillDir = node_path_1.default.join(dest, dirName);
|
|
@@ -1437,8 +1629,20 @@ function installOpencodeFamilySkills(runtime, targetDir, rawCommandsDir, pathPre
|
|
|
1437
1629
|
if (snap.size > 0)
|
|
1438
1630
|
toPreserve.set(dirName, snap);
|
|
1439
1631
|
}
|
|
1440
|
-
|
|
1632
|
+
// #5161: same ownership gate as installRuntimeArtifacts' skills kind. The
|
|
1633
|
+
// first-party set is rawDir's stems (exactly what this writer is about to
|
|
1634
|
+
// write); third-party capability skills carry the marker.
|
|
1635
|
+
// Same entry filter as the write loop below (a regular `.md` file), so a
|
|
1636
|
+
// name is owned here only if this writer is about to rewrite it.
|
|
1637
|
+
const rawStemNames = new Set(installFs().readdirSync(rawDir, { withFileTypes: true })
|
|
1638
|
+
.filter((e) => e.isFile() && e.name.endsWith('.md'))
|
|
1639
|
+
.map((e) => `${skillsKindEntry.prefix}${e.name.slice(0, -3)}`));
|
|
1640
|
+
const ownsSkillDir = createSkillDirOwnership(runtime, targetDir, dest, skillsKindEntry.prefix);
|
|
1641
|
+
_warnPreservedSkillDirs(dest,
|
|
1642
|
+
// Never pruned, as in installRuntimeArtifacts: the restore skips symlinks.
|
|
1643
|
+
_removeGsdEntries(dest, skillsKindEntry, (name) => !USER_OWNED_SKILL_DIRS.includes(name) && (rawStemNames.has(name) || ownsSkillDir(name))));
|
|
1441
1644
|
let count = 0;
|
|
1645
|
+
const written = new Set();
|
|
1442
1646
|
const firstPartyStems = new Set();
|
|
1443
1647
|
for (const entry of installFs().readdirSync(rawDir, { withFileTypes: true })) {
|
|
1444
1648
|
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
@@ -1453,6 +1657,7 @@ function installOpencodeFamilySkills(runtime, targetDir, rawCommandsDir, pathPre
|
|
|
1453
1657
|
const skillDir = node_path_1.default.join(dest, skillName);
|
|
1454
1658
|
installFs().mkdirSync(skillDir, { recursive: true });
|
|
1455
1659
|
installFs().writeFileSync(node_path_1.default.join(skillDir, 'SKILL.md'), content);
|
|
1660
|
+
written.add(skillName);
|
|
1456
1661
|
count++;
|
|
1457
1662
|
}
|
|
1458
1663
|
// #2362: materialize installed THIRD-PARTY capability skills, bound to their
|
|
@@ -1506,13 +1711,17 @@ function installOpencodeFamilySkills(runtime, targetDir, rawCommandsDir, pathPre
|
|
|
1506
1711
|
// capability is uninstalled/unsurfaced and no longer appears in any
|
|
1507
1712
|
// registry view.
|
|
1508
1713
|
installFs().writeFileSync(node_path_1.default.join(skillDir, installProfiles.CAPABILITY_SKILL_MARKER), found.capId + '\n', 'utf8');
|
|
1714
|
+
written.add(skillName);
|
|
1509
1715
|
count++;
|
|
1510
1716
|
}
|
|
1511
1717
|
}
|
|
1512
|
-
// Restore user-owned dirs after the prune+copy
|
|
1718
|
+
// Restore user-owned dirs after the prune+copy, only where a prune removed
|
|
1719
|
+
// one; the kept dir is left untouched (it may hold read-only files).
|
|
1513
1720
|
for (const [dirName, snap] of toPreserve) {
|
|
1514
|
-
|
|
1721
|
+
if (!installFs().existsSync(node_path_1.default.join(dest, dirName)))
|
|
1722
|
+
_restoreDir(node_path_1.default.join(dest, dirName), snap);
|
|
1515
1723
|
}
|
|
1724
|
+
recordWrittenSkillDirs(dest, written);
|
|
1516
1725
|
return count;
|
|
1517
1726
|
}
|
|
1518
1727
|
// ---------------------------------------------------------------------------
|
|
@@ -1544,7 +1753,7 @@ function installOpencodeFamilySkills(runtime, targetDir, rawCommandsDir, pathPre
|
|
|
1544
1753
|
* (`layout.kinds` → `agentsKindEntry.stage(resolvedProfile, agentCtx)` →
|
|
1545
1754
|
* `_copyStaged`), rather than forking a second agent-staging pipeline. A
|
|
1546
1755
|
* runtime/scope whose resolved layout declares no `agents` kind at all
|
|
1547
|
-
* (e.g. pi, whose `artifactLayout` is
|
|
1756
|
+
* (e.g. pi, whose `artifactLayout` is skills-only for both scopes, #5547) is a no-op
|
|
1548
1757
|
* (`null`) — mirrors `installOpencodeFamilySkills`'s own
|
|
1549
1758
|
* `if (!skillsKindEntry) return 0` contract.
|
|
1550
1759
|
*
|
|
@@ -2038,6 +2247,11 @@ module.exports = {
|
|
|
2038
2247
|
_runLegacyInstallMigrations,
|
|
2039
2248
|
_runLegacyUninstallCleanup,
|
|
2040
2249
|
_removeGsdEntries,
|
|
2250
|
+
createSkillDirOwnership,
|
|
2251
|
+
recordWrittenSkillDirs,
|
|
2252
|
+
clearWrittenSkillDirs,
|
|
2253
|
+
_warnPreservedSkillDirs,
|
|
2254
|
+
USER_OWNED_SKILL_DIRS,
|
|
2041
2255
|
_snapshotDir,
|
|
2042
2256
|
_restoreDir,
|
|
2043
2257
|
_removeHermesBareStemDirs,
|
|
@@ -302,7 +302,7 @@ function _capabilitySkillsForMode(mode, registry) {
|
|
|
302
302
|
/**
|
|
303
303
|
* Resolve a profile (or composed profiles) to a typed result object.
|
|
304
304
|
*/
|
|
305
|
-
function resolveProfile({ modes, manifest, _profilesOverride, registry } = {}) {
|
|
305
|
+
function resolveProfile({ modes, manifest, _profilesOverride, registry, agentManifest } = {}) {
|
|
306
306
|
const profiles = _profilesOverride || PROFILES;
|
|
307
307
|
const activeModes = (modes && modes.length > 0) ? modes : ['full'];
|
|
308
308
|
const normalizedModes = activeModes
|
|
@@ -337,10 +337,13 @@ function resolveProfile({ modes, manifest, _profilesOverride, registry } = {}) {
|
|
|
337
337
|
}
|
|
338
338
|
// Derive agents: union of all agent names referenced in the body text of
|
|
339
339
|
// every skill in unionSkills. Agent names are stored in the manifest under
|
|
340
|
-
// _calls_agents_<stem> keys (populated by loadSkillsManifest).
|
|
340
|
+
// _calls_agents_<stem> keys (populated by loadSkillsManifest). #5503: read from
|
|
341
|
+
// `agentManifest` when given, so a profile that skips the skill closure still
|
|
342
|
+
// stages the agents its own skills spawn.
|
|
343
|
+
const agentMan = agentManifest || man;
|
|
341
344
|
const unionAgents = new Set();
|
|
342
345
|
for (const skillStem of unionSkills) {
|
|
343
|
-
const agentRefs =
|
|
346
|
+
const agentRefs = agentMan.get(`_calls_agents_${skillStem}`) || [];
|
|
344
347
|
for (const agentStem of agentRefs) {
|
|
345
348
|
unionAgents.add(agentStem);
|
|
346
349
|
}
|
|
@@ -940,7 +943,7 @@ function stageSkillsForRuntimeAsSkills(srcCommandsDir, resolvedProfile, converte
|
|
|
940
943
|
* @param isGlobal install scope passed through to the converter
|
|
941
944
|
* @param agentCtx optional cross-cutting context (ADR-1235 §1)
|
|
942
945
|
*/
|
|
943
|
-
function stageAgentsForRuntimeWithConverter(srcAgentsDir, resolvedProfile, converter, isGlobal = false, agentCtx) {
|
|
946
|
+
function stageAgentsForRuntimeWithConverter(srcAgentsDir, resolvedProfile, converter, isGlobal = false, agentCtx, excludeSuffixes) {
|
|
944
947
|
if (!installFs().existsSync(srcAgentsDir))
|
|
945
948
|
return srcAgentsDir;
|
|
946
949
|
const stageDir = mkInstallTempDir('gsd-profile-runtime-agents-');
|
|
@@ -976,6 +979,8 @@ function stageAgentsForRuntimeWithConverter(srcAgentsDir, resolvedProfile, conve
|
|
|
976
979
|
continue;
|
|
977
980
|
if (!entry.name.endsWith('.md'))
|
|
978
981
|
continue;
|
|
982
|
+
if (excludeSuffixes && excludeSuffixes.some((s) => entry.name.endsWith(s)))
|
|
983
|
+
continue;
|
|
979
984
|
// For tiered profiles, gate by agent stem (full filename without extension).
|
|
980
985
|
if (resolvedProfile.skills !== '*') {
|
|
981
986
|
const stem = entry.name.slice(0, -3);
|
|
@@ -51,6 +51,8 @@ exports.BUNDLED_GSD_HOOK_FILES = Object.freeze(new Set([
|
|
|
51
51
|
// #3662: portable node resolver staged into hooks/ (not itself a lifecycle
|
|
52
52
|
// hook — managed JS hook commands route through it under --portable-hooks).
|
|
53
53
|
'hooks/gsd-node-runner.sh',
|
|
54
|
+
// #5465: SessionStart PATH persistence through $CLAUDE_ENV_FILE.
|
|
55
|
+
'hooks/gsd-path-export.js',
|
|
54
56
|
'hooks/gsd-phase-boundary.sh',
|
|
55
57
|
'hooks/gsd-prompt-guard.js',
|
|
56
58
|
'hooks/gsd-read-guard.js',
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MAX_SCOPED_EDIT_DISTANCE = void 0;
|
|
4
|
+
exports.matchLines = matchLines;
|
|
5
|
+
exports.writeScope = writeScope;
|
|
6
|
+
/**
|
|
7
|
+
* Line alignment for scoped Markdown normalization (#5165).
|
|
8
|
+
*
|
|
9
|
+
* `platformWriteSync` re-spaces a `.md` document on every write. Given the
|
|
10
|
+
* document the write replaces, only the line junctions the write itself made
|
|
11
|
+
* need that treatment; everything else is published byte-for-byte. This module
|
|
12
|
+
* is the pure half of that: it aligns the new lines against the prior lines and
|
|
13
|
+
* says which junctions the write left alone. It performs no I/O.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Largest line-edit distance `matchLines` will search for. A write that changed
|
|
17
|
+
* more than this many lines is a rewrite, not an edit: it falls back to
|
|
18
|
+
* whole-document normalization (the pre-#5165 behavior), which also bounds the
|
|
19
|
+
* diff's time and memory.
|
|
20
|
+
*/
|
|
21
|
+
exports.MAX_SCOPED_EDIT_DISTANCE = 400;
|
|
22
|
+
/**
|
|
23
|
+
* Align the lines of the document being written (`next`) against the document
|
|
24
|
+
* it replaces (`prev`). Returns, per `next` line, the index of its counterpart
|
|
25
|
+
* in `prev` (a longest-common-subsequence match, Myers' O(ND) greedy diff after
|
|
26
|
+
* trimming the common prefix and suffix), or -1 for a line the write added or
|
|
27
|
+
* changed. Null when the documents differ by more than MAX_SCOPED_EDIT_DISTANCE
|
|
28
|
+
* edits.
|
|
29
|
+
*/
|
|
30
|
+
function matchLines(prev, next) {
|
|
31
|
+
const match = new Int32Array(next.length).fill(-1);
|
|
32
|
+
const common = Math.min(prev.length, next.length);
|
|
33
|
+
let lo = 0;
|
|
34
|
+
while (lo < common && prev[lo] === next[lo]) {
|
|
35
|
+
match[lo] = lo;
|
|
36
|
+
lo++;
|
|
37
|
+
}
|
|
38
|
+
let prevEnd = prev.length;
|
|
39
|
+
let nextEnd = next.length;
|
|
40
|
+
while (prevEnd > lo && nextEnd > lo && prev[prevEnd - 1] === next[nextEnd - 1]) {
|
|
41
|
+
prevEnd--;
|
|
42
|
+
nextEnd--;
|
|
43
|
+
match[nextEnd] = prevEnd;
|
|
44
|
+
}
|
|
45
|
+
const a = prev.slice(lo, prevEnd);
|
|
46
|
+
const b = next.slice(lo, nextEnd);
|
|
47
|
+
const n = a.length;
|
|
48
|
+
const m = b.length;
|
|
49
|
+
if (n === 0 || m === 0)
|
|
50
|
+
return match;
|
|
51
|
+
const max = Math.min(n + m, exports.MAX_SCOPED_EDIT_DISTANCE);
|
|
52
|
+
const off = max + 1;
|
|
53
|
+
const v = new Int32Array(2 * max + 3);
|
|
54
|
+
const trace = [];
|
|
55
|
+
let found = -1;
|
|
56
|
+
for (let d = 0; d <= max && found < 0; d++) {
|
|
57
|
+
trace.push(v.slice()); // the furthest-reaching points after d-1 edits
|
|
58
|
+
for (let k = -d; k <= d; k += 2) {
|
|
59
|
+
let x = (k === -d || (k !== d && v[off + k - 1] < v[off + k + 1])) ? v[off + k + 1] : v[off + k - 1] + 1;
|
|
60
|
+
let y = x - k;
|
|
61
|
+
while (x < n && y < m && a[x] === b[y]) {
|
|
62
|
+
x++;
|
|
63
|
+
y++;
|
|
64
|
+
}
|
|
65
|
+
v[off + k] = x;
|
|
66
|
+
if (x >= n && y >= m) {
|
|
67
|
+
found = d;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (found < 0)
|
|
73
|
+
return null;
|
|
74
|
+
let x = n;
|
|
75
|
+
let y = m;
|
|
76
|
+
for (let d = found; d > 0; d--) {
|
|
77
|
+
const before = trace[d];
|
|
78
|
+
const k = x - y;
|
|
79
|
+
const prevK = (k === -d || (k !== d && before[off + k - 1] < before[off + k + 1])) ? k + 1 : k - 1;
|
|
80
|
+
const prevX = before[off + prevK];
|
|
81
|
+
const prevY = prevX - prevK;
|
|
82
|
+
while (x > prevX && y > prevY) {
|
|
83
|
+
x--;
|
|
84
|
+
y--;
|
|
85
|
+
match[lo + y] = lo + x;
|
|
86
|
+
}
|
|
87
|
+
x = prevX;
|
|
88
|
+
y = prevY;
|
|
89
|
+
}
|
|
90
|
+
while (x > 0 && y > 0) {
|
|
91
|
+
x--;
|
|
92
|
+
y--;
|
|
93
|
+
match[lo + y] = lo + x;
|
|
94
|
+
}
|
|
95
|
+
return match;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Derive a {@link WriteScope} from a line alignment. A junction is the write's
|
|
99
|
+
* own — and so is normalized — when it is wholly inside text the write added,
|
|
100
|
+
* or when a deletion between two kept lines made them newly adjacent. The
|
|
101
|
+
* boundary of an edit that replaced lines inherits the surrounding spacing:
|
|
102
|
+
* replacing a line in place must not add blank lines around it.
|
|
103
|
+
*/
|
|
104
|
+
function writeScope(match, prevLength) {
|
|
105
|
+
// For each unmatched line: how many prior lines its run of changed lines replaced.
|
|
106
|
+
const replaced = new Int32Array(match.length);
|
|
107
|
+
const priorIndex = new Int32Array(match.length).fill(-1);
|
|
108
|
+
for (let i = 0; i < match.length;) {
|
|
109
|
+
if (match[i] >= 0) {
|
|
110
|
+
i++;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
let end = i;
|
|
114
|
+
while (end + 1 < match.length && match[end + 1] < 0)
|
|
115
|
+
end++;
|
|
116
|
+
const before = i > 0 ? match[i - 1] : -1;
|
|
117
|
+
const after = end + 1 < match.length ? match[end + 1] : prevLength;
|
|
118
|
+
const dropped = after - before - 1;
|
|
119
|
+
for (let j = i; j <= end; j++) {
|
|
120
|
+
replaced[j] = dropped;
|
|
121
|
+
if (j - i < dropped)
|
|
122
|
+
priorIndex[j] = before + 1 + (j - i);
|
|
123
|
+
}
|
|
124
|
+
i = end + 1;
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
junctionKept(i) {
|
|
128
|
+
const left = match[i] >= 0;
|
|
129
|
+
const right = match[i + 1] >= 0;
|
|
130
|
+
// Two kept lines are one junction only if nothing sat between them in the
|
|
131
|
+
// prior document; a deletion made them adjacent, so the write owns it.
|
|
132
|
+
if (left && right)
|
|
133
|
+
return match[i + 1] - match[i] === 1;
|
|
134
|
+
if (left)
|
|
135
|
+
return replaced[i + 1] >= 1;
|
|
136
|
+
if (right)
|
|
137
|
+
return replaced[i] >= 1;
|
|
138
|
+
return false;
|
|
139
|
+
},
|
|
140
|
+
lineWritten(i) {
|
|
141
|
+
return match[i] < 0;
|
|
142
|
+
},
|
|
143
|
+
replacedPriorIndex(i) {
|
|
144
|
+
return priorIndex[i] ?? -1;
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|