@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
|
@@ -85,6 +85,14 @@
|
|
|
85
85
|
* [--no-archive-phases] Skip moving phase dirs to milestones/vX.Y-phases/ (archived by default)
|
|
86
86
|
* [--archive-quick] Move .planning/quick/* dirs to milestones/vX.Y-quick/ + reset the
|
|
87
87
|
* Quick Tasks Completed table (#2142; opt-in, default OFF)
|
|
88
|
+
* [--release-map <file>] Archive a per-repository release map (JSON array of
|
|
89
|
+
* {repo, release, reference, environment, status, tag}) as
|
|
90
|
+
* milestones/vX.Y-RELEASE-MAP.json and record it in MILESTONES.md
|
|
91
|
+
* (#5706; validated before any write, absent = unchanged)
|
|
92
|
+
*
|
|
93
|
+
* milestone release-tags --map <file.json> [--dry-run]
|
|
94
|
+
* Preflight, then create annotated tags per sub-repo from a JSON array of
|
|
95
|
+
* {repo, tag, ref?, message?, skip?}; never pushes, never overwrites (#5705)
|
|
88
96
|
*
|
|
89
97
|
* milestone archive-quick <version> Move .planning/quick/* dirs to milestones/vX.Y-quick/ + reset the
|
|
90
98
|
* Quick Tasks Completed table, WITHOUT the milestone complete close-out
|
|
@@ -326,6 +334,7 @@ const config = require('./lib/config.cjs');
|
|
|
326
334
|
const estimateCli = require('./lib/estimate-cli.cjs');
|
|
327
335
|
const template = require('./lib/template.cjs');
|
|
328
336
|
const milestone = require('./lib/milestone.cjs');
|
|
337
|
+
const milestoneReleaseTags = require('./lib/milestone-release-tags.cjs');
|
|
329
338
|
const commands = require('./lib/commands.cjs');
|
|
330
339
|
const runtimeIdentity = require('./lib/runtime-identity.cjs');
|
|
331
340
|
const init = require('./lib/init.cjs');
|
|
@@ -848,7 +857,31 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
848
857
|
error,
|
|
849
858
|
output: output,
|
|
850
859
|
});
|
|
851
|
-
if (
|
|
860
|
+
if (handled) return;
|
|
861
|
+
// #5268: this verb's flag surface is closed, as `todo complete`'s is
|
|
862
|
+
// (#4096). `--dry-run` is parsed and plumbed, and any OTHER flag fails loudly
|
|
863
|
+
// instead of being silently ignored: an accepted-but-ignored `--dry-run`
|
|
864
|
+
// wrote the config it was meant to preview. (`--raw` is spliced by the
|
|
865
|
+
// dispatcher before routing; it stays in the set as a guard against that
|
|
866
|
+
// splice ever moving.) The choices JSON is the one positional argument,
|
|
867
|
+
// wherever the flag sits, and a second one is refused for the same reason.
|
|
868
|
+
// A flag is a dash followed by a letter: `-1` is a (negative-number) JSON
|
|
869
|
+
// argument, refused below as choices that are not an object, not a flag.
|
|
870
|
+
const CONFIG_NEW_PROJECT_KNOWN_FLAGS = new Set(['--dry-run', '--raw']);
|
|
871
|
+
const positionals = [];
|
|
872
|
+
for (const a of args.slice(1)) {
|
|
873
|
+
if (typeof a === 'string' && /^--?[A-Za-z]/.test(a)) {
|
|
874
|
+
if (!CONFIG_NEW_PROJECT_KNOWN_FLAGS.has(a)) {
|
|
875
|
+
error(`Unknown flag for config-new-project: ${a}`, ERROR_REASON.USAGE);
|
|
876
|
+
}
|
|
877
|
+
continue;
|
|
878
|
+
}
|
|
879
|
+
positionals.push(a);
|
|
880
|
+
}
|
|
881
|
+
if (positionals.length > 1) {
|
|
882
|
+
error(`Unexpected argument for config-new-project: ${positionals[1]}. Usage: config-new-project [choices-json] [--dry-run]`, ERROR_REASON.USAGE);
|
|
883
|
+
}
|
|
884
|
+
config.cmdConfigNewProject(cwd, positionals[0], raw, { dryRun: args.includes('--dry-run') });
|
|
852
885
|
}
|
|
853
886
|
|
|
854
887
|
function routeConfigPath({ cwd, raw, workstreamContext }) {
|
|
@@ -1632,8 +1665,9 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1632
1665
|
// `plan`/`invoke` are the only subs that need the expensive plan-building path
|
|
1633
1666
|
// below; `sections`/`flags` return earlier still. Anything else errors here, before
|
|
1634
1667
|
// any of that work starts.
|
|
1635
|
-
|
|
1636
|
-
|
|
1668
|
+
const REVIEW_LANE_USAGE = 'Usage: review-lane <plan|invoke|sections|flags|availability|dispatch-step|explicit-from-argv> [--selected a,b] [--run-dir D] [--repo-root R]';
|
|
1669
|
+
if (!['plan', 'invoke', 'sections', 'flags', 'availability', 'dispatch-step', 'explicit-from-argv'].includes(sub)) {
|
|
1670
|
+
error(REVIEW_LANE_USAGE);
|
|
1637
1671
|
return;
|
|
1638
1672
|
}
|
|
1639
1673
|
const runDir = flag('--run-dir') || '.';
|
|
@@ -1709,8 +1743,34 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1709
1743
|
method: opts.method,
|
|
1710
1744
|
headers: opts.body ? { 'Content-Type': 'application/json' } : undefined,
|
|
1711
1745
|
body: opts.body,
|
|
1746
|
+
// Never follow a redirect: the egress check compares the CONFIGURED host, so a
|
|
1747
|
+
// followed redirect would send the review prompt to a host the user never named (#5433).
|
|
1748
|
+
redirect: 'manual',
|
|
1712
1749
|
signal: AbortSignal.timeout(opts.timeoutMs),
|
|
1713
1750
|
});
|
|
1751
|
+
// probe:true marks a reachability probe (returns status only, body ''): the status line is the answer, so do not
|
|
1752
|
+
// let a stalled body turn a replying server into `missing`.
|
|
1753
|
+
if (opts.probe) {
|
|
1754
|
+
res.body?.cancel().catch(() => {});
|
|
1755
|
+
return { ok: res.ok, status: res.status, body: '' };
|
|
1756
|
+
}
|
|
1757
|
+
if (res.status >= 300 && res.status < 400) {
|
|
1758
|
+
res.body?.cancel().catch(() => {});
|
|
1759
|
+
const location = res.headers.get('location');
|
|
1760
|
+
let target = 'without a Location header';
|
|
1761
|
+
if (location) {
|
|
1762
|
+
target = 'to an unparseable Location';
|
|
1763
|
+
try { target = `to ${new URL(location, url).host}`; } catch { /* keep the unparseable wording */ }
|
|
1764
|
+
}
|
|
1765
|
+
// Host and path only: a configured URL may carry credentials that must not reach a review file.
|
|
1766
|
+
const asked = (() => { try { const u = new URL(url); return `${u.host}${u.pathname}`; } catch { return 'the configured URL'; } })();
|
|
1767
|
+
return {
|
|
1768
|
+
ok: false,
|
|
1769
|
+
status: res.status,
|
|
1770
|
+
body: '',
|
|
1771
|
+
error: `${asked} answered HTTP ${res.status}, redirecting ${target}. The lane does not follow redirects; configure the final URL as the lane's host.`,
|
|
1772
|
+
};
|
|
1773
|
+
}
|
|
1714
1774
|
return { ok: res.ok, status: res.status, body: await res.text() };
|
|
1715
1775
|
} catch (e) {
|
|
1716
1776
|
return { ok: false, status: 0, body: '', error: e && e.message ? e.message : String(e) };
|
|
@@ -1737,6 +1797,23 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1737
1797
|
|
|
1738
1798
|
const selected = (flag('--selected') || '')
|
|
1739
1799
|
.split(',').map((s) => s.trim()).filter(Boolean);
|
|
1800
|
+
// #5266: `<slug>:available|missing` per openai-http lane. Host + default come from the lane
|
|
1801
|
+
// descriptor (resolveLanePlan), probing is runner.probeLane — review.md keeps no host table.
|
|
1802
|
+
// Prerequisite binaries (jq) are reported separately by the workflow, not folded in here.
|
|
1803
|
+
// First-party lanes only: ADR-2782 D5 requires egress/consent before a third-party lane's host is
|
|
1804
|
+
// contacted; detection has no such check, so installed third-party lanes are never probed here.
|
|
1805
|
+
if (sub === 'availability') {
|
|
1806
|
+
const { rows, warnings } = await runner.probeHttpLanes(
|
|
1807
|
+
REVIEWER_LANES,
|
|
1808
|
+
selected,
|
|
1809
|
+
(lane) => resolveLanePlan({ lane, configGet, runDir, repoRoot }),
|
|
1810
|
+
buildLaneRunnerDeps(),
|
|
1811
|
+
);
|
|
1812
|
+
for (const w of warnings) process.stderr.write(`review-lane availability: ${w}\n`);
|
|
1813
|
+
process.stdout.write(rows.join('\n') + (rows.length ? '\n' : ''));
|
|
1814
|
+
return;
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1740
1817
|
// ADR-2782 D8 (#2927): the lane map is first-party ∪ INSTALLED overlay
|
|
1741
1818
|
// `reviewer` bodies, first-party winning on slug collision. Before this merge
|
|
1742
1819
|
// the map was built from the frozen REVIEWER_LANES array alone, so an installed,
|
|
@@ -2001,7 +2078,7 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
2001
2078
|
}
|
|
2002
2079
|
|
|
2003
2080
|
if (sub !== 'invoke') {
|
|
2004
|
-
error(
|
|
2081
|
+
error(REVIEW_LANE_USAGE);
|
|
2005
2082
|
return;
|
|
2006
2083
|
}
|
|
2007
2084
|
|
|
@@ -2141,6 +2218,13 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
2141
2218
|
testCommandNormalizer.cmdNormalizeTestCommand(cwd, args[1]);
|
|
2142
2219
|
}
|
|
2143
2220
|
|
|
2221
|
+
function routeResolvePackageManager({ args, cwd }) {
|
|
2222
|
+
// #5346: the gates' no-configured-command fallback asks here for the
|
|
2223
|
+
// project's package manager (packageManager field, then lockfile, else npm).
|
|
2224
|
+
// args[1] is the script name; --cwd (already parsed into `cwd`) locates package.json.
|
|
2225
|
+
require('./lib/normalize-test-command.cjs').cmdResolvePackageManager(cwd, args[1]);
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2144
2228
|
function routeDispatchShouldFlatten({ args, cwd, raw, error }) {
|
|
2145
2229
|
// #1708 / #853: typed query replacing the `RUNTIME === 'codex'` prose rule.
|
|
2146
2230
|
//
|
|
@@ -3142,7 +3226,18 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
3142
3226
|
// --dry-run) the command refuses before touching anything. Distinct
|
|
3143
3227
|
// from --force, which bypasses the narrow scope guards only.
|
|
3144
3228
|
const confirm = args.includes('--confirm');
|
|
3145
|
-
|
|
3229
|
+
// #5706 (epic #3628): optional caller-supplied release map file.
|
|
3230
|
+
// A flag with no usable value fails loud rather than being dropped.
|
|
3231
|
+
let releaseMap = null;
|
|
3232
|
+
const releaseMapIdx = args.indexOf('--release-map');
|
|
3233
|
+
if (releaseMapIdx !== -1) {
|
|
3234
|
+
const releaseMapValue = args[releaseMapIdx + 1];
|
|
3235
|
+
if (typeof releaseMapValue !== 'string' || releaseMapValue.startsWith('--') || releaseMapValue.trim() === '') {
|
|
3236
|
+
error('--release-map requires a file path (a JSON array of release-map entries)');
|
|
3237
|
+
}
|
|
3238
|
+
releaseMap = releaseMapValue;
|
|
3239
|
+
}
|
|
3240
|
+
milestone.cmdMilestoneComplete(cwd, args[2], { name: milestoneName, archivePhases, force, dryRun, archiveQuick, confirm, releaseMap }, raw);
|
|
3146
3241
|
} else if (subcommand === 'archive-quick') {
|
|
3147
3242
|
// #2142 escalation: narrow archival-only entry point (does NOT
|
|
3148
3243
|
// touch ROADMAP/REQUIREMENTS/MILESTONES.md, runs no completion
|
|
@@ -3150,8 +3245,11 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
3150
3245
|
// unlike `milestone complete --archive-quick`.
|
|
3151
3246
|
const dryRun = args.includes('--dry-run');
|
|
3152
3247
|
milestone.cmdQuickArchive(cwd, args[2], { dryRun }, raw);
|
|
3248
|
+
} else if (subcommand === 'release-tags') {
|
|
3249
|
+
// #5705 (epic #3628 Phase 1): repository-scoped annotated release tags.
|
|
3250
|
+
milestoneReleaseTags.cmdMilestoneReleaseTags(cwd, args, raw);
|
|
3153
3251
|
} else {
|
|
3154
|
-
error('Unknown milestone subcommand. Available: complete, archive-quick', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
3252
|
+
error('Unknown milestone subcommand. Available: complete, archive-quick, release-tags', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
3155
3253
|
}
|
|
3156
3254
|
}
|
|
3157
3255
|
|
|
@@ -4809,6 +4907,7 @@ const HOST_COMMAND_ROUTERS = {
|
|
|
4809
4907
|
// #3676 (Phase 4, epic #3344): quick-batch coordination verbs.
|
|
4810
4908
|
'quick-batch': routeQuickBatchCommand,
|
|
4811
4909
|
'normalize-test-command': routeNormalizeTestCommand,
|
|
4910
|
+
'resolve-package-manager': routeResolvePackageManager,
|
|
4812
4911
|
'dispatch-should-flatten': routeDispatchShouldFlatten,
|
|
4813
4912
|
'dispatch-isolation': routeDispatchIsolation,
|
|
4814
4913
|
'dispatch-capacity': routeDispatchCapacity,
|
|
@@ -5114,7 +5213,7 @@ function runWithTimeout(argv) {
|
|
|
5114
5213
|
// drifting apart when a query command was added to only one or two.
|
|
5115
5214
|
const TOP_LEVEL_USAGE = 'Usage: gsd-tools <command> [args] [--raw] [--pick <field>] [--cwd <path>] [--project-dir <path>] [--ws <name>] [--json-errors] [--exit-contract=<v>]\n' +
|
|
5116
5215
|
'Commands: agent, agent-skills, assumption-delta, audit-open, audit-uat, check, check-commit, commit, commit-docs-guard, commit-to-subrepo, pr-subrepo, ' +
|
|
5117
|
-
'config-ensure-section, config-get, config-new-project, config-path, config-set, migrate-config, normalize-test-command, ' +
|
|
5216
|
+
'config-ensure-section, config-get, config-new-project, config-path, config-set, migrate-config, normalize-test-command, resolve-package-manager, ' +
|
|
5118
5217
|
'context-predicates, current-timestamp, detect-custom-files, docs-init, drift-guard, effort, extract-messages, find-phase, ' +
|
|
5119
5218
|
'from-gsd2, frontmatter, gap-analysis, generate-claude-md, generate-claude-profile, ' +
|
|
5120
5219
|
'generate-dev-preferences, generate-slug, graphify, history-digest, init, intel, ' +
|
|
@@ -328,8 +328,33 @@ function checkCodexModelPosture(runtime, projectRoot) {
|
|
|
328
328
|
// derives its sandbox_mode from actually lives, in every install shape
|
|
329
329
|
// (repo-dev tree or npm-global package, where the package still ships its
|
|
330
330
|
// own agents/ alongside bin/ and gsd-core/).
|
|
331
|
-
|
|
332
|
-
|
|
331
|
+
//
|
|
332
|
+
// #5496: "install-relative" has three shapes. This module compiles to
|
|
333
|
+
// `<root>/gsd-core/bin/lib/`, so `<root>` is three parents up. In the repository
|
|
334
|
+
// and the npm package the canonical sources are `<root>/agents`. In an INSTALLED
|
|
335
|
+
// config dir `<root>/agents` holds the CONVERTED runtime copies (no `tools:`
|
|
336
|
+
// declaration — every role then derived `read-only`, 26 false violations on a
|
|
337
|
+
// clean Codex install) and the canonical sources are `<root>/gsd-core/agents`.
|
|
338
|
+
// Prefer the nested directory when it actually holds agent sources; fall back to
|
|
339
|
+
// `<root>/agents`. `root` is a parameter so each layout is testable.
|
|
340
|
+
//
|
|
341
|
+
// #5636: "holds agent sources" is a recognized agent file, `gsd-<name>.md` (every file in the shipped
|
|
342
|
+
// roster) that is not a `.compact.md` variant. Any markdown would let a stray README in the nested
|
|
343
|
+
// directory hide the real sources, so the posture check found no role's canonical file and skipped all.
|
|
344
|
+
const CANONICAL_AGENT_SOURCE_FILE = /^gsd-.+\.md$/;
|
|
345
|
+
function isCanonicalAgentSourceFile(entry) {
|
|
346
|
+
return CANONICAL_AGENT_SOURCE_FILE.test(entry) && !entry.endsWith('.compact.md');
|
|
347
|
+
}
|
|
348
|
+
function canonicalAgentSourceDir(root = node_path_1.default.join(__dirname, '..', '..', '..')) {
|
|
349
|
+
const nested = node_path_1.default.join(root, 'gsd-core', 'agents');
|
|
350
|
+
try {
|
|
351
|
+
if (node_fs_1.default.readdirSync(nested).some(isCanonicalAgentSourceFile))
|
|
352
|
+
return nested;
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
// Absent or unreadable: not this layout.
|
|
356
|
+
}
|
|
357
|
+
return node_path_1.default.join(root, 'agents');
|
|
333
358
|
}
|
|
334
359
|
/**
|
|
335
360
|
* Validate installed Codex `.toml` agents' `sandbox_mode` against what each
|
|
@@ -384,7 +409,7 @@ function checkCodexSandboxPosture(runtime, projectRoot) {
|
|
|
384
409
|
reason: POSTURE_REASON.AGENTS_DIR_MISSING,
|
|
385
410
|
};
|
|
386
411
|
}
|
|
387
|
-
const canonicalAgentsDir =
|
|
412
|
+
const canonicalAgentsDir = canonicalAgentSourceDir();
|
|
388
413
|
// Same symlink guard as checkCodexModelPosture, same reason (never follow
|
|
389
414
|
// an agents-dir symlink into an arbitrary file's content).
|
|
390
415
|
const tomlFiles = node_fs_1.default
|
|
@@ -533,6 +558,7 @@ module.exports = {
|
|
|
533
558
|
checkAgentsInstalled,
|
|
534
559
|
checkCodexModelPosture,
|
|
535
560
|
checkCodexSandboxPosture,
|
|
561
|
+
canonicalAgentSourceDir,
|
|
536
562
|
POSTURE_REASON,
|
|
537
563
|
resolveAgentHint,
|
|
538
564
|
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Per-install terminators for the typed-text capture heredocs (#5637).
|
|
4
|
+
*
|
|
5
|
+
* Every workflow that reads the typed text opens with
|
|
6
|
+
*
|
|
7
|
+
* IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
|
|
8
|
+
* $ARGUMENTS
|
|
9
|
+
* GSD_ARGS_END
|
|
10
|
+
*
|
|
11
|
+
* and `/gsd-quick-batch` feeds `parse-args` through the same shape with `GSD_QUICK_BATCH_ARGS_END`.
|
|
12
|
+
* The harness replaces `$ARGUMENTS` with the typed text before the shell parses the fence, so typed
|
|
13
|
+
* text with a terminator line of its own closes the heredoc early and the lines after it run as
|
|
14
|
+
* shell. A literal in a shipped file cannot be made unforgeable, so each install replaces it with
|
|
15
|
+
* `<TERMINATOR>_<nonce>`, a nonce the typed text has no way to know. The shipped source keeps the
|
|
16
|
+
* canonical tokens; only the files an install writes carry the nonce.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.ARGS_NONCE_RE = exports.ARGS_TERMINATOR = exports.ARGS_TERMINATORS = void 0;
|
|
20
|
+
exports.generateArgsNonce = generateArgsNonce;
|
|
21
|
+
exports.stampArgsTerminator = stampArgsTerminator;
|
|
22
|
+
exports.readInstalledNonce = readInstalledNonce;
|
|
23
|
+
exports.armArgsNonce = armArgsNonce;
|
|
24
|
+
exports.disarmArgsNonce = disarmArgsNonce;
|
|
25
|
+
exports.stampInstalledContent = stampInstalledContent;
|
|
26
|
+
const node_crypto_1 = require("node:crypto");
|
|
27
|
+
const node_fs_1 = require("node:fs");
|
|
28
|
+
const node_path_1 = require("node:path");
|
|
29
|
+
/** The canonical terminators the shipped workflows and commands carry. */
|
|
30
|
+
exports.ARGS_TERMINATORS = Object.freeze(['GSD_ARGS_END', 'GSD_QUICK_BATCH_ARGS_END']);
|
|
31
|
+
/** The terminator every workflow carries. */
|
|
32
|
+
exports.ARGS_TERMINATOR = 'GSD_ARGS_END';
|
|
33
|
+
/** A nonce is 128 random bits as 32 lowercase hex characters. */
|
|
34
|
+
exports.ARGS_NONCE_RE = /^[0-9a-f]{32}$/;
|
|
35
|
+
const OPENER_RE = new RegExp(`<<'(${exports.ARGS_TERMINATORS.join('|')})'`, 'g');
|
|
36
|
+
const CLOSER_RE = new RegExp(`^(${exports.ARGS_TERMINATORS.join('|')})(?=\\r?$)`, 'gm');
|
|
37
|
+
/** A fresh nonce. `randomBytes` is a seam for tests. */
|
|
38
|
+
function generateArgsNonce(randomBytes = node_crypto_1.randomBytes) {
|
|
39
|
+
return randomBytes(16).toString('hex');
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* `content` with each canonical terminator replaced by the stamped one: the heredoc opener tag and
|
|
43
|
+
* the closing line. A terminator that is already stamped does not match and is left alone, and so
|
|
44
|
+
* is prose that names a token inside a line.
|
|
45
|
+
*
|
|
46
|
+
* @throws {TypeError} when `nonce` is not 32 lowercase hex characters
|
|
47
|
+
*/
|
|
48
|
+
function stampArgsTerminator(content, nonce) {
|
|
49
|
+
if (typeof nonce !== 'string' || !exports.ARGS_NONCE_RE.test(nonce)) {
|
|
50
|
+
throw new TypeError('stampArgsTerminator: nonce must be 32 lowercase hex characters');
|
|
51
|
+
}
|
|
52
|
+
return content
|
|
53
|
+
.replace(OPENER_RE, (_m, token) => `<<'${token}_${nonce}'`)
|
|
54
|
+
.replace(CLOSER_RE, (_m, token) => `${token}_${nonce}`);
|
|
55
|
+
}
|
|
56
|
+
let armedNonce = null;
|
|
57
|
+
const INSTALLED_NONCE_RE = /^GSD_ARGS_END_([0-9a-f]{32})\r?$/m;
|
|
58
|
+
/** A workflow is tens of KiB; anything near this is not one, and is not read. */
|
|
59
|
+
const MAX_WORKFLOW_BYTES = 2 * 1024 * 1024;
|
|
60
|
+
/**
|
|
61
|
+
* The nonce a previous install left in `configDir`, read from the first installed workflow that
|
|
62
|
+
* carries one; null when there is none. Entries that are not regular files (a directory, a FIFO,
|
|
63
|
+
* a symlink) and files over MAX_WORKFLOW_BYTES are skipped, and one unreadable entry does not end
|
|
64
|
+
* the scan.
|
|
65
|
+
*/
|
|
66
|
+
function readInstalledNonce(configDir) {
|
|
67
|
+
let dir;
|
|
68
|
+
let names;
|
|
69
|
+
try {
|
|
70
|
+
dir = (0, node_path_1.join)(configDir, 'gsd-core', 'workflows');
|
|
71
|
+
names = (0, node_fs_1.readdirSync)(dir).sort();
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
for (const name of names) {
|
|
77
|
+
if (!name.endsWith('.md'))
|
|
78
|
+
continue;
|
|
79
|
+
try {
|
|
80
|
+
const full = (0, node_path_1.join)(dir, name);
|
|
81
|
+
const stat = (0, node_fs_1.lstatSync)(full);
|
|
82
|
+
if (!stat.isFile() || stat.size > MAX_WORKFLOW_BYTES)
|
|
83
|
+
continue;
|
|
84
|
+
const match = INSTALLED_NONCE_RE.exec((0, node_fs_1.readFileSync)(full, 'utf8'));
|
|
85
|
+
if (match)
|
|
86
|
+
return match[1];
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// This entry is unreadable; the next one may still carry the nonce.
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Arm stamping for the install into `configDir`, before it writes anything. When `reuse` is true
|
|
96
|
+
* the directory's installed nonce is kept, so a reinstall changes nothing; otherwise, or when
|
|
97
|
+
* there is none, a fresh one is minted. Only a global install (a directory under the user's home,
|
|
98
|
+
* which a cloned repository cannot seed) may reuse: a project-local directory is often committed,
|
|
99
|
+
* and a nonce planted there would be adopted. Generators never arm it, so the committed generated
|
|
100
|
+
* skills stay canonical and deterministic.
|
|
101
|
+
*/
|
|
102
|
+
function armArgsNonce(configDir, reuse = false) {
|
|
103
|
+
const prior = reuse && configDir !== undefined ? readInstalledNonce(configDir) : null;
|
|
104
|
+
armedNonce = prior ?? generateArgsNonce();
|
|
105
|
+
return armedNonce;
|
|
106
|
+
}
|
|
107
|
+
/** Stop stamping: the process is no longer writing for an install that has a nonce. */
|
|
108
|
+
function disarmArgsNonce() {
|
|
109
|
+
armedNonce = null;
|
|
110
|
+
}
|
|
111
|
+
/** `content` stamped with the armed nonce, or untouched when this process is not an installer. */
|
|
112
|
+
function stampInstalledContent(content) {
|
|
113
|
+
return armedNonce === null ? content : stampArgsTerminator(content, armedNonce);
|
|
114
|
+
}
|
|
@@ -504,6 +504,63 @@ function validateEntryShape(e, i) {
|
|
|
504
504
|
...('milestone' in o ? { milestone: typeof o.milestone === 'string' ? o.milestone : null } : {}),
|
|
505
505
|
};
|
|
506
506
|
}
|
|
507
|
+
/**
|
|
508
|
+
* #5418: when the frontmatter counts disagree with the parsed entries, the usual
|
|
509
|
+
* cause is a hand-edited table: a row added to the rendered table (and the counts
|
|
510
|
+
* bumped to match) while the fenced JSON block, the sole source of entries, was left
|
|
511
|
+
* behind. Names the row ids the table and the JSON block disagree about so the
|
|
512
|
+
* counts message does not point at the (correct) frontmatter. Returns '' when there
|
|
513
|
+
* is nothing to add. Best-effort by design: it runs while a WindowsError is already
|
|
514
|
+
* on its way out, so it must never throw and replace that error.
|
|
515
|
+
*/
|
|
516
|
+
/** Row ids spelled out in the divergence note; the rest are counted. */
|
|
517
|
+
const ROW_ID_LIST_LIMIT = 20;
|
|
518
|
+
function describeTableJsonDivergence(raw, entries) {
|
|
519
|
+
try {
|
|
520
|
+
// The hint is the PARSED entry count, not frontmatter total_count: the latter
|
|
521
|
+
// is the number that is wrong here.
|
|
522
|
+
const table = extractTableRegion(raw, entries.length);
|
|
523
|
+
if (table === null)
|
|
524
|
+
return '';
|
|
525
|
+
const rowId = (line) => (line.split('|')[1] ?? '').trim();
|
|
526
|
+
// Ids are positive integers (the JSON side enforces it), so a first cell that is
|
|
527
|
+
// not one — the `_(none)_` empty-ledger placeholder, a continuation line of a
|
|
528
|
+
// multi-line description, control characters — is not a row id and is never
|
|
529
|
+
// echoed into the message.
|
|
530
|
+
const tableIds = new Set();
|
|
531
|
+
const tableLines = table.split('\n');
|
|
532
|
+
for (let i = 2; i < tableLines.length; i++) {
|
|
533
|
+
if (!tableLines[i].startsWith('|'))
|
|
534
|
+
continue;
|
|
535
|
+
const id = rowId(tableLines[i]);
|
|
536
|
+
if (/^\d{1,15}$/.test(id))
|
|
537
|
+
tableIds.add(id);
|
|
538
|
+
}
|
|
539
|
+
const jsonIds = new Set(entries.map((e) => String(e.id)));
|
|
540
|
+
const byNumber = (a, b) => Number(a) - Number(b);
|
|
541
|
+
const list = (ids) => {
|
|
542
|
+
const shown = ids.slice(0, ROW_ID_LIST_LIMIT).join(', ');
|
|
543
|
+
return ids.length > ROW_ID_LIST_LIMIT ? `${shown} and ${ids.length - ROW_ID_LIST_LIMIT} more` : shown;
|
|
544
|
+
};
|
|
545
|
+
const tableOnly = [...tableIds].filter((id) => !jsonIds.has(id)).sort(byNumber);
|
|
546
|
+
const jsonOnly = [...jsonIds].filter((id) => !tableIds.has(id)).sort(byNumber);
|
|
547
|
+
const parts = [];
|
|
548
|
+
if (tableOnly.length > 0) {
|
|
549
|
+
parts.push(`row id(s) in the table but not in the fenced JSON block: ${list(tableOnly)}`);
|
|
550
|
+
}
|
|
551
|
+
if (jsonOnly.length > 0) {
|
|
552
|
+
parts.push(`row id(s) in the fenced JSON block but not in the table: ${list(jsonOnly)}`);
|
|
553
|
+
}
|
|
554
|
+
if (parts.length === 0)
|
|
555
|
+
return '';
|
|
556
|
+
return ` The table and the fenced JSON block disagree (${parts.join('; ')}). The JSON block is the ` +
|
|
557
|
+
'authoritative entries source: add the missing entries there (or remove the stray table rows) ' +
|
|
558
|
+
'and keep the frontmatter counts in step with it.';
|
|
559
|
+
}
|
|
560
|
+
catch {
|
|
561
|
+
return '';
|
|
562
|
+
}
|
|
563
|
+
}
|
|
507
564
|
function parseLedger(raw) {
|
|
508
565
|
const fm = parseFrontmatterStrict(raw);
|
|
509
566
|
if (fm.schema_version !== exports.SCHEMA_VERSION) {
|
|
@@ -537,7 +594,8 @@ function parseLedger(raw) {
|
|
|
537
594
|
recomputed.total_count !== ledger.total_count) {
|
|
538
595
|
throw new WindowsError(exports.REASON.WINDOWS_LEDGER_MALFORMED, `Ledger counts disagree with entries: frontmatter open/waived/fixed/total=` +
|
|
539
596
|
`${ledger.open_count}/${ledger.waived_count}/${ledger.fixed_count}/${ledger.total_count}` +
|
|
540
|
-
` but entries yield ${recomputed.open_count}/${recomputed.waived_count}/${recomputed.fixed_count}/${recomputed.total_count}.`
|
|
597
|
+
` but entries yield ${recomputed.open_count}/${recomputed.waived_count}/${recomputed.fixed_count}/${recomputed.total_count}.` +
|
|
598
|
+
describeTableJsonDivergence(raw, entries));
|
|
541
599
|
}
|
|
542
600
|
return ledger;
|
|
543
601
|
}
|
|
@@ -225,10 +225,18 @@ let _selfProbeAttempts = 0;
|
|
|
225
225
|
function selfStartTime() {
|
|
226
226
|
if (_selfStartTime === null && _selfProbeAttempts < SELF_PROBE_ATTEMPTS) {
|
|
227
227
|
_selfProbeAttempts++;
|
|
228
|
-
|
|
228
|
+
// Through the same injectable probe the steal path uses, so the value acquireLock records and the
|
|
229
|
+
// value a test pins are one source (a hard-wired live probe made the recorded value depend on
|
|
230
|
+
// PowerShell's cold start, which turned `next` red on a Windows runner).
|
|
231
|
+
_selfStartTime = _lockProbes.getProcessStartTime(process.pid);
|
|
229
232
|
}
|
|
230
233
|
return _selfStartTime;
|
|
231
234
|
}
|
|
235
|
+
/** Forget the cached self start-time and its attempt count (the probe it came from was just replaced). */
|
|
236
|
+
function resetSelfStartTime() {
|
|
237
|
+
_selfStartTime = null;
|
|
238
|
+
_selfProbeAttempts = 0;
|
|
239
|
+
}
|
|
232
240
|
/** Serialize the lockfile body: JSON carrying the owner token, pid, hostname, start-time, ts. */
|
|
233
241
|
function lockFileBody(token, startTime) {
|
|
234
242
|
return JSON.stringify({ token, pid: process.pid, hostname: node_os_1.default.hostname(), startTime, ts: Date.now() });
|
|
@@ -571,11 +579,14 @@ module.exports = {
|
|
|
571
579
|
_setLockProbes(probes) {
|
|
572
580
|
if (typeof probes.isPidAlive === 'function')
|
|
573
581
|
_lockProbes.isPidAlive = probes.isPidAlive;
|
|
574
|
-
if (typeof probes.getProcessStartTime === 'function')
|
|
582
|
+
if (typeof probes.getProcessStartTime === 'function') {
|
|
575
583
|
_lockProbes.getProcessStartTime = probes.getProcessStartTime;
|
|
584
|
+
resetSelfStartTime();
|
|
585
|
+
}
|
|
576
586
|
},
|
|
577
587
|
_resetLockProbes() {
|
|
578
588
|
_lockProbes.isPidAlive = _realIsPidAlive;
|
|
579
589
|
_lockProbes.getProcessStartTime = getProcessStartTime;
|
|
590
|
+
resetSelfStartTime();
|
|
580
591
|
},
|
|
581
592
|
};
|