gentle-pi 2.1.2 → 2.3.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/README.md +108 -79
- package/assets/agents/gentle-ai-worker.md +7 -3
- package/assets/agents/jd-fix-agent.md +1 -1
- package/assets/agents/jd-judge-a.md +3 -1
- package/assets/agents/jd-judge-b.md +3 -1
- package/assets/agents/review-readability.md +4 -3
- package/assets/agents/review-reliability.md +4 -3
- package/assets/agents/review-resilience.md +4 -3
- package/assets/agents/review-risk.md +4 -3
- package/assets/agents/sdd-apply.md +11 -4
- package/assets/agents/sdd-archive.md +6 -1
- package/assets/agents/sdd-design.md +6 -1
- package/assets/agents/sdd-explore.md +6 -2
- package/assets/agents/sdd-init.md +10 -2
- package/assets/agents/sdd-onboard.md +6 -1
- package/assets/agents/sdd-proposal.md +8 -1
- package/assets/agents/sdd-research.md +54 -0
- package/assets/agents/sdd-spec.md +6 -1
- package/assets/agents/sdd-status.md +10 -5
- package/assets/agents/sdd-sync.md +6 -1
- package/assets/agents/sdd-tasks.md +8 -4
- package/assets/agents/sdd-verify.md +27 -2
- package/assets/chains/4r-review.chain.md +2 -0
- package/assets/chains/sdd-full.chain.md +2 -2
- package/assets/chains/sdd-plan.chain.md +1 -1
- package/assets/chains/sdd-verify.chain.md +2 -2
- package/assets/orchestrator-delegation.md +145 -160
- package/assets/orchestrator-memory.md +2 -0
- package/assets/orchestrator.md +25 -48
- package/assets/sdd-orchestrator-workflow.md +163 -25
- package/assets/support/sdd-status-contract.md +24 -6
- package/contracts/review-integration/v1/fixtures/consent.fixture.json +3 -3
- package/contracts/review-integration/v1/fixtures/start-v2.fixture.json +19 -28
- package/contracts/review-integration/v1/fixtures/start.fixture.json +1 -10
- package/contracts/review-integration/v1/fixtures/status-v2.fixture.json +12 -21
- package/contracts/review-integration/v1/schemas/correction-plan-request.schema.json +49 -0
- package/contracts/review-integration/v1/schemas/operation.schema.json +76 -0
- package/contracts/review-integration/v1/schemas/repair.schema.json +39 -0
- package/contracts/review-integration/v1/schemas/status-v2.schema.json +4 -2
- package/contracts/review-integration/v1/schemas/status.schema.json +4 -2
- package/contracts/review-integration/v1/schemas/transition-execution.schema.json +42 -0
- package/contracts/review-integration/v2/fixtures/consent.fixture.json +1 -1
- package/contracts/review-integration/v2/fixtures/start.fixture.json +1 -10
- package/contracts/review-integration/v2/fixtures/status.fixture.json +1 -10
- package/contracts/review-integration/v2/schemas/failure.schema.json +5 -1
- package/contracts/review-integration/v2/schemas/last-event-closure.schema.json +66 -0
- package/contracts/review-integration/v2/schemas/opencode-provider-role.schema.json +14 -0
- package/contracts/review-integration/v2/schemas/operation.schema.json +6 -1
- package/contracts/review-integration/v2/schemas/repair.schema.json +4 -2
- package/contracts/review-integration/v2/schemas/start.schema.json +5 -2
- package/contracts/review-integration/v2/schemas/status.schema.json +4 -2
- package/contracts/review-provider-contract-mirror/provider-contract.lock.json +30 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/README.md +12 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/manifest.json +65 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/lens.schema.json +16 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/refuter.schema.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/targeted-validator.schema.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/lens.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/refuter.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/targeted-validator.json +1 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/generated/provider-capabilities.baseline.json +15 -0
- package/contracts/review-provider-contract-mirror/v1.1.0/generated/provider-roles.baseline.json +42 -0
- package/docs/native-authority-architecture.md +14 -16
- package/docs/review-integration.md +27 -353
- package/extensions/ask-user-choice.ts +151 -0
- package/extensions/gentle-ai.ts +2710 -3211
- package/extensions/quiet-tools.ts +515 -32
- package/extensions/sdd-init.ts +21 -12
- package/extensions/skill-registry.ts +10 -2
- package/extensions/startup-banner.ts +10 -4
- package/lib/gentle-ai-binary.ts +173 -2
- package/lib/gentle-ai-renderer.ts +70 -0
- package/lib/model-routing-authority.ts +133 -0
- package/lib/native-review-cli.ts +676 -781
- package/lib/opaque-pi-reviewer-adapter.ts +284 -0
- package/lib/provider-contract-bundle.ts +704 -0
- package/lib/review-candidate-view.ts +836 -118
- package/lib/review-compact-contract.ts +59 -248
- package/lib/review-host-relay.ts +578 -0
- package/lib/review-integration-v2.ts +1254 -221
- package/lib/review-last-event-controller.ts +35 -0
- package/lib/review-relay-contract.ts +16 -0
- package/lib/sdd-preflight.ts +230 -67
- package/lib/sdd-status.ts +66 -111
- package/lib/terminal-theme.ts +1 -1
- package/package.json +83 -79
- package/runtime/gentle-ai-binary.mjs +174 -3
- package/runtime/native-review-cli.mjs +610 -715
- package/runtime/review-integration-v2.mjs +1223 -190
- package/runtime/review-relay-contract.mjs +17 -0
- package/scripts/{build-git-commit-transaction-runner.mjs → build-runtime-modules.mjs} +5 -5
- package/scripts/check-provider-contract.mjs +138 -0
- package/scripts/gentle-ai-installer.mjs +85 -22
- package/scripts/maintainer/provider-relay-matrix.mjs +403 -0
- package/scripts/mirror-provider-contract.mjs +143 -0
- package/scripts/test-packed-runner.mjs +20 -9
- package/scripts/verify-package-files.mjs +119 -43
- package/skills/_shared/review-ledger-contract.md +11 -19
- package/skills/chained-pr/SKILL.md +3 -0
- package/skills/cognitive-doc-design/SKILL.md +1 -1
- package/skills/comment-writer/SKILL.md +1 -1
- package/skills/gentle-ai/SKILL.md +7 -74
- package/skills/issue-creation/SKILL.md +94 -168
- package/skills/judgment-day/SKILL.md +9 -5
- package/skills/judgment-day/references/prompts-and-formats.md +2 -0
- package/skills/rdd-defect-workflow/SKILL.md +54 -0
- package/skills/release/SKILL.md +3 -3
- package/skills/skill-registry/SKILL.md +1 -1
- package/skills/work-unit-commits/SKILL.md +3 -1
- package/tests/artifact-language.test.ts +24 -11
- package/tests/ask-user-choice.test.ts +264 -0
- package/tests/background-subagents.test.ts +771 -0
- package/tests/codegraph-tools.test.ts +3 -3
- package/tests/crosslane/cross-lane.mjs +16 -0
- package/tests/delegated-key-learnings-contract.test.ts +240 -0
- package/tests/devbinary/native-review-parity.devtest.ts +167 -250
- package/tests/devbinary/pi-host-relay.devtest.ts +867 -0
- package/tests/fixtures/devbinary/capabilities-v2.1.derived.json +331 -0
- package/tests/fixtures/devbinary/capabilities-v2.2.captured.json +340 -0
- package/tests/fixtures/devbinary/consent-v3.captured.json +37 -0
- package/tests/fixtures/devbinary/failure-v2-capture-evidence.captured.json +16 -0
- package/tests/fixtures/devbinary/last-event-capture-correction-plan.captured.json +10 -0
- package/tests/fixtures/devbinary/last-event-capture-refuter-approved.captured.json +20 -0
- package/tests/fixtures/devbinary/last-event-capture-refuter-correction-required.captured.json +26 -0
- package/tests/fixtures/devbinary/last-event-capture-result-approved.captured.json +8 -0
- package/tests/fixtures/devbinary/last-event-capture-result-correction-required.captured.json +26 -0
- package/tests/fixtures/devbinary/last-event-capture-validation-approved.captured.json +8 -0
- package/tests/fixtures/devbinary/last-event-closure.provenance.md +13 -0
- package/tests/fixtures/devbinary/result-artifact-v2-path.captured.json +12 -0
- package/tests/fixtures/devbinary/result-artifact-v2.captured.json +12 -0
- package/tests/fixtures/devbinary/review-acknowledged-v1.captured.json +9 -0
- package/tests/fixtures/devbinary/review-acknowledged.provenance.md +31 -0
- package/tests/fixtures/devbinary/start-v3-consent-declined.captured.json +19 -0
- package/tests/fixtures/devbinary/start-v3-consent-granted.captured.json +109 -0
- package/tests/fixtures/devbinary/start-v3-zero-lens-closed.captured.json +21 -0
- package/tests/fixtures/devbinary/status-v5-capture-result-submission.captured.json +184 -0
- package/tests/fixtures/devbinary/status-v5-repository-context.captured.json +138 -0
- package/tests/fixtures/devbinary/status-v5.captured.json +88 -0
- package/tests/fixtures/native-review-cli/v2.5.0-rc.1/PROVENANCE.txt +15 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/README.md +12 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/manifest.json +65 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/lens.schema.json +16 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/refuter.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/targeted-validator.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/lens.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/refuter.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/targeted-validator.json +1 -0
- package/tests/gentle-ai-binary.test.ts +83 -13
- package/tests/gentle-ai-dev-binary-surfacing.test.ts +195 -0
- package/tests/gentle-ai-dev-binary.test.ts +159 -0
- package/tests/gentle-ai-installer.test.ts +138 -48
- package/tests/gentle-ai.test.ts +820 -131
- package/tests/gentle-theme.test.ts +133 -0
- package/tests/maintainer/provider-relay.maintest.ts +601 -0
- package/tests/model-routing-authority.test.ts +257 -0
- package/tests/native-review-capability-contract.test.ts +75 -2
- package/tests/native-review-cli.test.ts +588 -908
- package/tests/native-review-consent.test.ts +283 -38
- package/tests/native-review-parity-runtime.test.ts +108 -350
- package/tests/native-review-parity.test.ts +684 -732
- package/tests/native-sdd-attempt-authority.test.ts +235 -0
- package/tests/opaque-pi-reviewer-adapter.test.ts +266 -0
- package/tests/orchestrator-budget.test.ts +158 -89
- package/tests/orchestrator-rdd-ownership.test.ts +103 -0
- package/tests/package-manifest.test.ts +128 -100
- package/tests/provider-contract-bundle.test.ts +385 -0
- package/tests/provider-contract-mirror.test.ts +206 -0
- package/tests/provider-defect-handoff.test.ts +252 -0
- package/tests/quiet-tool-rendering.test.ts +1055 -28
- package/tests/review-actor-tool-deny.test.ts +12 -13
- package/tests/review-authority-recovery-docs.test.ts +1 -2
- package/tests/review-candidate-view.test.ts +898 -12
- package/tests/review-compact-contract.test.ts +29 -122
- package/tests/review-controller-lock-status.test.ts +2 -2
- package/tests/review-controller-native-recovery.test.ts +366 -857
- package/tests/review-controller-native-routing.test.ts +1253 -4199
- package/tests/review-controller-retired-ops.test.ts +1 -1
- package/tests/review-controller-workspace-root.test.ts +236 -70
- package/tests/review-controller.test.ts +26 -816
- package/tests/review-corrected-finalize-binding.test.ts +134 -0
- package/tests/review-dispatch-hydration-gap.test.ts +145 -0
- package/tests/review-gate.test.ts +0 -45
- package/tests/review-host-relay-restart-parity.test.ts +360 -0
- package/tests/review-host-relay-routing.test.ts +352 -0
- package/tests/review-host-relay.test.ts +754 -0
- package/tests/review-integration-v2-forward.test.ts +654 -0
- package/tests/review-integration-v2.test.ts +317 -129
- package/tests/review-last-event-closure.test.ts +408 -0
- package/tests/review-ledger-contract.test.ts +106 -60
- package/tests/review-recovered-lineage-routing.test.ts +199 -0
- package/tests/review-relay-transport-agent.test.ts +322 -0
- package/tests/review-snapshot.test.ts +3 -2
- package/tests/runtime-harness.mjs +573 -180
- package/tests/sdd-agent-tools.test.ts +53 -32
- package/tests/sdd-preflight.test.ts +81 -15
- package/tests/sdd-status.test.ts +109 -110
- package/tests/skill-collision-prefixes.test.ts +6 -8
- package/tests/skill-registry.test.ts +50 -1
- package/tests/verify-package-files.test.ts +62 -0
- package/tests/writer-edit-surface-scope.test.ts +230 -0
- package/themes/Gentleman-Cute.json +94 -0
- package/themes/Gentleman-Sexy.json +92 -0
- package/assets/agents/review-refuter.md +0 -40
- package/assets/agents/review-validator.md +0 -23
- package/lib/git-commit-transaction.ts +0 -801
- package/lib/native-review-remediation.ts +0 -49
- package/lib/review-compact.ts +0 -947
- package/lib/review-refuter-adapter.ts +0 -129
- package/lib/review-runtime-contract.ts +0 -68
- package/prompts/gcl.md +0 -54
- package/prompts/gis.md +0 -25
- package/prompts/gpr.md +0 -41
- package/prompts/gwr.md +0 -31
- package/runtime/git-commit-transaction.mjs +0 -802
- package/scripts/run-git-commit-transaction.mjs +0 -35
- package/tests/fixtures/native-review-cli/v2.1.2/bind-sdd.json +0 -25
- package/tests/fixtures/native-review-cli/v2.1.2/finalize.json +0 -8
- package/tests/fixtures/native-review-cli/v2.1.2/sdd-status-engram.json +0 -139
- package/tests/fixtures/native-review-cli/v2.1.2/sdd-status.json +0 -200
- package/tests/fixtures/native-review-cli/v2.1.2/start.json +0 -12
- package/tests/fixtures/native-review-cli/v2.1.2/validate-allow.json +0 -24
- package/tests/fixtures/native-review-cli/v2.1.2/validate-deny-empty-context.json +0 -20
- package/tests/fixtures/native-review-cli/v2.1.2/validate-deny.json +0 -28
- package/tests/fixtures/native-review-cli/v2.1.3/sdd-status-engram.json +0 -139
- package/tests/fixtures/native-review-cli/v2.1.3/sdd-status.json +0 -200
- package/tests/git-commit-transaction.test.ts +0 -302
- package/tests/review-compact.test.ts +0 -243
- package/tests/review-refuter-adapter.test.ts +0 -89
package/tests/sdd-status.test.ts
CHANGED
|
@@ -92,69 +92,24 @@ test("resolveSddStatus selects the only active change and counts task progress",
|
|
|
92
92
|
assert.match(status.instructions?.apply.join("\n") ?? "", /persisted task checkboxes/);
|
|
93
93
|
});
|
|
94
94
|
|
|
95
|
-
test("resolveSddStatus
|
|
95
|
+
test("resolveSddStatus routes completed implementation through archive without RDD authority", async () => {
|
|
96
96
|
const cwd = await workspace();
|
|
97
97
|
const root = seedChange(cwd);
|
|
98
98
|
write(join(root, "tasks.md"), "# Tasks\n\n- [x] 1.1 Done\n");
|
|
99
99
|
write(join(root, "verify-report.md"), "# Verify\n\nPASS\n");
|
|
100
100
|
write(join(root, "sync-report.md"), "# Sync\n\nPASS\n");
|
|
101
|
-
let resolverCalls = 0;
|
|
102
|
-
const planning = resolveSddStatus({
|
|
103
|
-
cwd,
|
|
104
|
-
changeName: "add-auth",
|
|
105
|
-
reviewAuthority: {
|
|
106
|
-
expected: false,
|
|
107
|
-
resolve: () => {
|
|
108
|
-
resolverCalls += 1;
|
|
109
|
-
return { activeAuthorityId: "must-not-run" };
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
});
|
|
113
|
-
assert.equal(resolverCalls, 0);
|
|
114
|
-
assert.equal(planning.blockedReasons.some((reason) => reason.startsWith("resolve-review:")), false);
|
|
115
|
-
|
|
116
|
-
const invalid = resolveSddStatus({
|
|
117
|
-
cwd,
|
|
118
|
-
changeName: "add-auth",
|
|
119
|
-
reviewAuthority: {
|
|
120
|
-
expected: true,
|
|
121
|
-
resolve: () => {
|
|
122
|
-
throw new Error("stale compact successor");
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
});
|
|
126
|
-
assert.equal(invalid.applyState, "blocked");
|
|
127
|
-
assert.equal(invalid.dependencies.archive, "blocked");
|
|
128
|
-
assert.equal(invalid.nextRecommended, "resolve-review");
|
|
129
|
-
assert.match(invalid.blockedReasons.join("\n"), /^resolve-review: stale compact successor/m);
|
|
130
101
|
|
|
131
|
-
const
|
|
132
|
-
cwd,
|
|
133
|
-
changeName: "add-auth",
|
|
134
|
-
reviewAuthority: {
|
|
135
|
-
expected: true,
|
|
136
|
-
resolve: () => ({ activeAuthorityId: "validated-compact-authority" }),
|
|
137
|
-
},
|
|
138
|
-
});
|
|
139
|
-
assert.equal(valid.applyState, "all_done");
|
|
140
|
-
assert.equal(valid.dependencies.archive, "ready");
|
|
141
|
-
assert.equal(valid.blockedReasons.some((reason) => reason.startsWith("resolve-review:")), false);
|
|
142
|
-
});
|
|
102
|
+
const status = resolveSddStatus({ cwd, changeName: "add-auth" });
|
|
143
103
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
nativeReviewReadiness: { expected: true, ready: false, reason: "binding is missing" },
|
|
151
|
-
});
|
|
152
|
-
assert.equal(status.applyState, "blocked");
|
|
153
|
-
assert.equal(status.nextRecommended, "resolve-review");
|
|
154
|
-
assert.deepEqual(status.blockedReasons, ["resolve-review: binding is missing"]);
|
|
104
|
+
assert.equal(status.applyState, "all_done");
|
|
105
|
+
assert.equal(status.dependencies.verify, "all_done");
|
|
106
|
+
assert.equal(status.dependencies.sync, "all_done");
|
|
107
|
+
assert.equal(status.dependencies.archive, "ready");
|
|
108
|
+
assert.equal(status.nextRecommended, "sdd-archive");
|
|
109
|
+
assert.equal(status.blockedReasons.some((reason) => reason.startsWith("resolve-review:")), false);
|
|
155
110
|
});
|
|
156
111
|
|
|
157
|
-
test("resolveSddStatus
|
|
112
|
+
test("resolveSddStatus ignores legacy parent review actions after implementation", async () => {
|
|
158
113
|
const cwd = await workspace();
|
|
159
114
|
const root = seedChange(cwd);
|
|
160
115
|
write(
|
|
@@ -174,8 +129,8 @@ test("resolveSddStatus routes completed implementation to parent lifecycle and s
|
|
|
174
129
|
assert.deepEqual(status.taskArtifactErrors, []);
|
|
175
130
|
assert.equal(status.applyState, "all_done");
|
|
176
131
|
assert.equal(status.dependencies.apply, "all_done");
|
|
177
|
-
assert.equal(status.
|
|
178
|
-
assert.
|
|
132
|
+
assert.equal(status.dependencies.verify, "ready");
|
|
133
|
+
assert.equal(status.nextRecommended, "sdd-verify");
|
|
179
134
|
});
|
|
180
135
|
|
|
181
136
|
test("resolveSddStatus treats malformed ownership as unresolved implementation work", async () => {
|
|
@@ -197,7 +152,7 @@ test("resolveSddStatus treats malformed ownership as unresolved implementation w
|
|
|
197
152
|
assert.match(status.blockedReasons.join("\n"), /task ownership marker/i);
|
|
198
153
|
});
|
|
199
154
|
|
|
200
|
-
test("resolveSddStatus routes completed legacy implementation to
|
|
155
|
+
test("resolveSddStatus routes completed legacy implementation directly to verify", async () => {
|
|
201
156
|
const cwd = await workspace();
|
|
202
157
|
const root = seedChange(cwd);
|
|
203
158
|
write(join(root, "tasks.md"), "# Tasks\n\n- [x] 1.1 Build foundation\n");
|
|
@@ -206,7 +161,8 @@ test("resolveSddStatus routes completed legacy implementation to parent lifecycl
|
|
|
206
161
|
|
|
207
162
|
assert.equal(status.applyState, "all_done");
|
|
208
163
|
assert.equal(status.dependencies.apply, "all_done");
|
|
209
|
-
assert.equal(status.
|
|
164
|
+
assert.equal(status.dependencies.verify, "ready");
|
|
165
|
+
assert.equal(status.nextRecommended, "sdd-verify");
|
|
210
166
|
});
|
|
211
167
|
|
|
212
168
|
test("resolveSddStatus blocks sync when verify report is not clearly passing", async () => {
|
|
@@ -215,11 +171,7 @@ test("resolveSddStatus blocks sync when verify report is not clearly passing", a
|
|
|
215
171
|
write(join(root, "apply-progress.md"), "# Apply\n\nSome work completed.\n");
|
|
216
172
|
write(join(root, "verify-report.md"), "# Verify\n\nTODO: tests not run yet\n");
|
|
217
173
|
|
|
218
|
-
const status = resolveSddStatus({
|
|
219
|
-
cwd,
|
|
220
|
-
changeName: "add-auth",
|
|
221
|
-
reviewAuthority: { expected: true, resolve: () => ({ activeAuthorityId: "approved" }) },
|
|
222
|
-
});
|
|
174
|
+
const status = resolveSddStatus({ cwd, changeName: "add-auth" });
|
|
223
175
|
|
|
224
176
|
assert.equal(status.dependencies.verify, "ready");
|
|
225
177
|
assert.equal(status.dependencies.sync, "blocked");
|
|
@@ -233,11 +185,7 @@ test("resolveSddStatus rejects negated pass and sync-complete phrases", async ()
|
|
|
233
185
|
write(join(root, "verify-report.md"), "# Verify\n\nStatus: not passed\n");
|
|
234
186
|
write(join(root, "sync-report.md"), "# Sync\n\nSync complete: no\n");
|
|
235
187
|
|
|
236
|
-
const status = resolveSddStatus({
|
|
237
|
-
cwd,
|
|
238
|
-
changeName: "add-auth",
|
|
239
|
-
reviewAuthority: { expected: true, resolve: () => ({ activeAuthorityId: "approved" }) },
|
|
240
|
-
});
|
|
188
|
+
const status = resolveSddStatus({ cwd, changeName: "add-auth" });
|
|
241
189
|
|
|
242
190
|
assert.equal(status.dependencies.verify, "ready");
|
|
243
191
|
assert.equal(status.dependencies.sync, "blocked");
|
|
@@ -334,11 +282,7 @@ test("resolveSddStatus blocks stale sync report when current verify is not passi
|
|
|
334
282
|
write(join(root, "verify-report.md"), "# Verify\n\nStatus: not passed\n");
|
|
335
283
|
write(join(root, "sync-report.md"), "# Sync\n\nPASS\n");
|
|
336
284
|
|
|
337
|
-
const status = resolveSddStatus({
|
|
338
|
-
cwd,
|
|
339
|
-
changeName: "add-auth",
|
|
340
|
-
reviewAuthority: { expected: true, resolve: () => ({ activeAuthorityId: "approved" }) },
|
|
341
|
-
});
|
|
285
|
+
const status = resolveSddStatus({ cwd, changeName: "add-auth" });
|
|
342
286
|
|
|
343
287
|
assert.equal(status.dependencies.verify, "ready");
|
|
344
288
|
assert.equal(status.dependencies.sync, "blocked");
|
|
@@ -381,11 +325,7 @@ test("resolveSddStatus marks archive ready only after clean verify, sync, and co
|
|
|
381
325
|
write(join(root, "verify-report.md"), "# Verify\n\nPASS\n");
|
|
382
326
|
write(join(root, "sync-report.md"), "# Sync\n\nPASS\n");
|
|
383
327
|
|
|
384
|
-
const status = resolveSddStatus({
|
|
385
|
-
cwd,
|
|
386
|
-
changeName: "add-auth",
|
|
387
|
-
reviewAuthority: { expected: true, resolve: () => ({ activeAuthorityId: "approved" }) },
|
|
388
|
-
});
|
|
328
|
+
const status = resolveSddStatus({ cwd, changeName: "add-auth" });
|
|
389
329
|
|
|
390
330
|
assert.equal(status.dependencies.archive, "ready");
|
|
391
331
|
assert.equal(status.nextRecommended, "sdd-archive");
|
|
@@ -428,13 +368,13 @@ test("resolveSddStatus with artifactStore none returns non-authoritative status
|
|
|
428
368
|
assert.equal(status.nextRecommended, "resolve-via-engram");
|
|
429
369
|
});
|
|
430
370
|
|
|
431
|
-
test("resolveSddStatus with artifactStore
|
|
371
|
+
test("resolveSddStatus with artifactStore hybrid uses disk scan and reflects store", async () => {
|
|
432
372
|
const cwd = await workspace();
|
|
433
373
|
seedChange(cwd);
|
|
434
374
|
|
|
435
|
-
const status = resolveSddStatus({ cwd, artifactStore: "
|
|
375
|
+
const status = resolveSddStatus({ cwd, artifactStore: "hybrid", changeName: "add-auth" });
|
|
436
376
|
|
|
437
|
-
assert.equal(status.artifactStore, "
|
|
377
|
+
assert.equal(status.artifactStore, "hybrid");
|
|
438
378
|
assert.equal(status.changeName, "add-auth");
|
|
439
379
|
assert.notEqual(status.nextRecommended, "resolve-via-engram");
|
|
440
380
|
});
|
|
@@ -484,13 +424,13 @@ test("parseSddStatusCommandArgs extracts change and json flag", () => {
|
|
|
484
424
|
});
|
|
485
425
|
});
|
|
486
426
|
|
|
487
|
-
test("resolveSddStatus with artifactStore
|
|
427
|
+
test("resolveSddStatus with artifactStore hybrid and NO openspec dir returns non-authoritative status", async () => {
|
|
488
428
|
const cwd = await workspace();
|
|
489
|
-
// No openspec directory —
|
|
429
|
+
// No openspec directory — the hybrid store without disk backing is non-authoritative
|
|
490
430
|
|
|
491
|
-
const status = resolveSddStatus({ cwd, artifactStore: "
|
|
431
|
+
const status = resolveSddStatus({ cwd, artifactStore: "hybrid", changeName: "my-change" });
|
|
492
432
|
|
|
493
|
-
assert.equal(status.artifactStore, "
|
|
433
|
+
assert.equal(status.artifactStore, "hybrid");
|
|
494
434
|
assert.equal(status.changeName, "my-change");
|
|
495
435
|
assert.deepEqual(status.blockedReasons, []);
|
|
496
436
|
assert.equal(status.nextRecommended, "resolve-via-engram");
|
|
@@ -499,13 +439,13 @@ test("resolveSddStatus with artifactStore both and NO openspec dir returns non-a
|
|
|
499
439
|
assert.equal(status.dependencies.archive, "not_applicable");
|
|
500
440
|
});
|
|
501
441
|
|
|
502
|
-
test("resolveSddStatus with artifactStore
|
|
442
|
+
test("resolveSddStatus with artifactStore hybrid and existing openspec dir runs authoritative disk scan", async () => {
|
|
503
443
|
const cwd = await workspace();
|
|
504
444
|
seedChange(cwd);
|
|
505
445
|
|
|
506
|
-
const status = resolveSddStatus({ cwd, artifactStore: "
|
|
446
|
+
const status = resolveSddStatus({ cwd, artifactStore: "hybrid", changeName: "add-auth" });
|
|
507
447
|
|
|
508
|
-
assert.equal(status.artifactStore, "
|
|
448
|
+
assert.equal(status.artifactStore, "hybrid");
|
|
509
449
|
assert.equal(status.changeName, "add-auth");
|
|
510
450
|
assert.notEqual(status.nextRecommended, "resolve-via-engram");
|
|
511
451
|
assert.equal(status.artifacts.proposal, "done");
|
|
@@ -526,13 +466,13 @@ test("isNonAuthoritativeStatus reads typed isNonAuthoritative field and matches
|
|
|
526
466
|
assert.equal(isNonAuthoritativeStatus(none), true);
|
|
527
467
|
assert.equal(none.nextRecommended, "resolve-via-engram");
|
|
528
468
|
|
|
529
|
-
const bothWithoutOpenspec = resolveSddStatus({ cwd, artifactStore: "
|
|
469
|
+
const bothWithoutOpenspec = resolveSddStatus({ cwd, artifactStore: "hybrid", changeName: "x" });
|
|
530
470
|
assert.equal(bothWithoutOpenspec.isNonAuthoritative, true);
|
|
531
471
|
assert.equal(isNonAuthoritativeStatus(bothWithoutOpenspec), true);
|
|
532
472
|
assert.equal(bothWithoutOpenspec.nextRecommended, "resolve-via-engram");
|
|
533
473
|
|
|
534
474
|
seedChange(cwd);
|
|
535
|
-
const bothWithOpenspec = resolveSddStatus({ cwd, artifactStore: "
|
|
475
|
+
const bothWithOpenspec = resolveSddStatus({ cwd, artifactStore: "hybrid", changeName: "add-auth" });
|
|
536
476
|
assert.equal(bothWithOpenspec.isNonAuthoritative, false);
|
|
537
477
|
assert.equal(isNonAuthoritativeStatus(bothWithOpenspec), false);
|
|
538
478
|
assert.notEqual(bothWithOpenspec.nextRecommended, "resolve-via-engram");
|
|
@@ -551,12 +491,12 @@ test("isNonAuthoritative boolean field is set correctly across all store/disk co
|
|
|
551
491
|
assert.equal(none.isNonAuthoritative, true);
|
|
552
492
|
|
|
553
493
|
// both without openspec/ → non-authoritative
|
|
554
|
-
const bothWithout = resolveSddStatus({ cwd, artifactStore: "
|
|
494
|
+
const bothWithout = resolveSddStatus({ cwd, artifactStore: "hybrid", changeName: "x" });
|
|
555
495
|
assert.equal(bothWithout.isNonAuthoritative, true);
|
|
556
496
|
|
|
557
497
|
// both WITH openspec/ and seeded change → authoritative
|
|
558
498
|
seedChange(cwd);
|
|
559
|
-
const bothWith = resolveSddStatus({ cwd, artifactStore: "
|
|
499
|
+
const bothWith = resolveSddStatus({ cwd, artifactStore: "hybrid", changeName: "add-auth" });
|
|
560
500
|
assert.equal(bothWith.isNonAuthoritative, false);
|
|
561
501
|
|
|
562
502
|
// openspec (default disk scan, seeded) → authoritative
|
|
@@ -565,30 +505,30 @@ test("isNonAuthoritative boolean field is set correctly across all store/disk co
|
|
|
565
505
|
});
|
|
566
506
|
|
|
567
507
|
// Fix 4 item 1 — both + openspec/ dir present + change NOT on disk → non-authoritative
|
|
568
|
-
test("resolveSddStatus with artifactStore
|
|
508
|
+
test("resolveSddStatus with artifactStore hybrid, openspec dir present but change not on disk returns non-authoritative", async () => {
|
|
569
509
|
const cwd = await workspace();
|
|
570
510
|
// Create an openspec/changes dir with a different change — not the requested one
|
|
571
511
|
mkdirSync(join(cwd, "openspec", "changes", "other-change"), { recursive: true });
|
|
572
512
|
|
|
573
|
-
const status = resolveSddStatus({ cwd, artifactStore: "
|
|
513
|
+
const status = resolveSddStatus({ cwd, artifactStore: "hybrid", changeName: "missing-change" });
|
|
574
514
|
|
|
575
515
|
assert.equal(status.isNonAuthoritative, true);
|
|
576
516
|
assert.equal(status.nextRecommended, "resolve-via-engram");
|
|
577
517
|
assert.deepEqual(status.blockedReasons, []);
|
|
578
518
|
assert.equal(status.applyState, "not_applicable");
|
|
579
|
-
assert.equal(status.artifactStore, "
|
|
519
|
+
assert.equal(status.artifactStore, "hybrid");
|
|
580
520
|
// Must NOT be treated as blocked
|
|
581
521
|
assert.notEqual(status.applyState, "blocked");
|
|
582
522
|
});
|
|
583
523
|
|
|
584
524
|
// Fix 4 item 2 — strengthen existing both-with-openspec-and-seeded-change test
|
|
585
|
-
test("resolveSddStatus with artifactStore
|
|
525
|
+
test("resolveSddStatus with artifactStore hybrid, openspec dir present and change on disk is authoritative", async () => {
|
|
586
526
|
const cwd = await workspace();
|
|
587
527
|
seedChange(cwd);
|
|
588
528
|
|
|
589
|
-
const status = resolveSddStatus({ cwd, artifactStore: "
|
|
529
|
+
const status = resolveSddStatus({ cwd, artifactStore: "hybrid", changeName: "add-auth" });
|
|
590
530
|
|
|
591
|
-
assert.equal(status.artifactStore, "
|
|
531
|
+
assert.equal(status.artifactStore, "hybrid");
|
|
592
532
|
assert.equal(status.changeName, "add-auth");
|
|
593
533
|
// Must be authoritative
|
|
594
534
|
assert.equal(isNonAuthoritativeStatus(status), false);
|
|
@@ -618,7 +558,7 @@ test("renderSddDispatcherMarkdown for both-without-openspec does NOT render Read
|
|
|
618
558
|
const cwd = await workspace();
|
|
619
559
|
// No openspec directory — both store is non-authoritative
|
|
620
560
|
|
|
621
|
-
const status = resolveSddStatus({ cwd, artifactStore: "
|
|
561
|
+
const status = resolveSddStatus({ cwd, artifactStore: "hybrid", changeName: "fix-x" });
|
|
622
562
|
const markdown = renderSddDispatcherMarkdown(status);
|
|
623
563
|
|
|
624
564
|
assert.doesNotMatch(markdown, /### Ready/);
|
|
@@ -628,7 +568,7 @@ test("renderSddDispatcherMarkdown for both-without-openspec does NOT render Read
|
|
|
628
568
|
test("renderNativeSddPhasePrompt for both-without-openspec emits non-authoritative line", async () => {
|
|
629
569
|
const cwd = await workspace();
|
|
630
570
|
|
|
631
|
-
const status = resolveSddStatus({ cwd, artifactStore: "
|
|
571
|
+
const status = resolveSddStatus({ cwd, artifactStore: "hybrid", changeName: "fix-x" });
|
|
632
572
|
const prompt = renderNativeSddPhasePrompt(status, "apply");
|
|
633
573
|
|
|
634
574
|
assert.match(prompt, /non-authoritative/);
|
|
@@ -646,17 +586,17 @@ test("renderPhaseInstructions for not_applicable applyState emits neutral line",
|
|
|
646
586
|
});
|
|
647
587
|
|
|
648
588
|
// Fix 4 item 1 — both + openspec/ + ZERO changes + no changeName → non-authoritative
|
|
649
|
-
test("resolveSddStatus
|
|
589
|
+
test("resolveSddStatus hybrid + openspec/ dir + zero active changes + no changeName returns non-authoritative", async () => {
|
|
650
590
|
const cwd = await workspace();
|
|
651
591
|
// openspec/ dir exists but holds no active changes (only the changes/ subdir)
|
|
652
592
|
mkdirSync(join(cwd, "openspec", "changes"), { recursive: true });
|
|
653
593
|
|
|
654
|
-
const status = resolveSddStatus({ cwd, artifactStore: "
|
|
594
|
+
const status = resolveSddStatus({ cwd, artifactStore: "hybrid" });
|
|
655
595
|
|
|
656
596
|
assert.equal(status.isNonAuthoritative, true);
|
|
657
597
|
assert.equal(status.nextRecommended, "resolve-via-engram");
|
|
658
598
|
assert.deepEqual(status.blockedReasons, []);
|
|
659
|
-
assert.equal(status.artifactStore, "
|
|
599
|
+
assert.equal(status.artifactStore, "hybrid");
|
|
660
600
|
assert.equal(status.applyState, "not_applicable");
|
|
661
601
|
assert.equal(status.dependencies.apply, "not_applicable");
|
|
662
602
|
assert.equal(status.dependencies.archive, "not_applicable");
|
|
@@ -665,12 +605,12 @@ test("resolveSddStatus both + openspec/ dir + zero active changes + no changeNam
|
|
|
665
605
|
});
|
|
666
606
|
|
|
667
607
|
// Fix 4 item 2 — both + openspec/ + MULTIPLE changes + no changeName → authoritative select-change
|
|
668
|
-
test("resolveSddStatus
|
|
608
|
+
test("resolveSddStatus hybrid + openspec/ dir + multiple active changes + no changeName stays authoritative", async () => {
|
|
669
609
|
const cwd = await workspace();
|
|
670
610
|
mkdirSync(join(cwd, "openspec", "changes", "alpha"), { recursive: true });
|
|
671
611
|
mkdirSync(join(cwd, "openspec", "changes", "beta"), { recursive: true });
|
|
672
612
|
|
|
673
|
-
const status = resolveSddStatus({ cwd, artifactStore: "
|
|
613
|
+
const status = resolveSddStatus({ cwd, artifactStore: "hybrid" });
|
|
674
614
|
|
|
675
615
|
// Authoritative ambiguous-selection behavior must be preserved
|
|
676
616
|
assert.equal(status.isNonAuthoritative, false);
|
|
@@ -679,16 +619,16 @@ test("resolveSddStatus both + openspec/ dir + multiple active changes + no chang
|
|
|
679
619
|
});
|
|
680
620
|
|
|
681
621
|
// Fix 4 item 3 — both + openspec/ + ONE resolvable change → authoritative
|
|
682
|
-
test("resolveSddStatus
|
|
622
|
+
test("resolveSddStatus hybrid + openspec/ dir + exactly one active change is authoritative", async () => {
|
|
683
623
|
const cwd = await workspace();
|
|
684
624
|
seedChange(cwd);
|
|
685
625
|
|
|
686
626
|
// No changeName supplied — should auto-select the single change
|
|
687
|
-
const status = resolveSddStatus({ cwd, artifactStore: "
|
|
627
|
+
const status = resolveSddStatus({ cwd, artifactStore: "hybrid" });
|
|
688
628
|
|
|
689
629
|
assert.equal(status.isNonAuthoritative, false);
|
|
690
630
|
assert.equal(status.changeName, "add-auth");
|
|
691
|
-
assert.equal(status.artifactStore, "
|
|
631
|
+
assert.equal(status.artifactStore, "hybrid");
|
|
692
632
|
assert.notEqual(status.applyState, "not_applicable");
|
|
693
633
|
assert.notEqual(status.nextRecommended, "resolve-via-engram");
|
|
694
634
|
assert.equal(status.artifacts.proposal, "done");
|
|
@@ -719,14 +659,73 @@ test("resolveSddStatus openspec + named change missing still blocks", async () =
|
|
|
719
659
|
assert.notEqual(status.nextRecommended, "resolve-via-engram");
|
|
720
660
|
});
|
|
721
661
|
|
|
662
|
+
// Issue #535 — post-archive status exposes explicit completion and the archived artifact location
|
|
663
|
+
test("resolveSddStatus projects archived terminal state when the change lives in the archive", async () => {
|
|
664
|
+
const cwd = await workspace();
|
|
665
|
+
mkdirSync(join(cwd, "openspec", "changes"), { recursive: true });
|
|
666
|
+
write(
|
|
667
|
+
join(cwd, "openspec", "changes", "archive", "2026-01-02-add-auth", "proposal.md"),
|
|
668
|
+
"# Proposal\n",
|
|
669
|
+
);
|
|
670
|
+
|
|
671
|
+
const status = resolveSddStatus({ cwd, changeName: "add-auth" });
|
|
672
|
+
|
|
673
|
+
assert.equal(status.changeName, "add-auth");
|
|
674
|
+
assert.equal(status.nextRecommended, "archived");
|
|
675
|
+
assert.deepEqual(status.archived, {
|
|
676
|
+
path: join("openspec", "changes", "archive", "2026-01-02-add-auth"),
|
|
677
|
+
});
|
|
678
|
+
assert.deepEqual(status.blockedReasons, []);
|
|
679
|
+
assert.equal(status.dependencies.archive, "all_done");
|
|
680
|
+
assert.doesNotMatch(status.nextRecommended, /sdd-new|Start an SDD change|not found/i);
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
test("resolveSddStatus archived projection prefers the newest archive date", async () => {
|
|
684
|
+
const cwd = await workspace();
|
|
685
|
+
mkdirSync(join(cwd, "openspec", "changes"), { recursive: true });
|
|
686
|
+
mkdirSync(join(cwd, "openspec", "changes", "archive", "2026-01-01-add-auth"), { recursive: true });
|
|
687
|
+
mkdirSync(join(cwd, "openspec", "changes", "archive", "2026-03-04-add-auth"), { recursive: true });
|
|
688
|
+
|
|
689
|
+
const status = resolveSddStatus({ cwd, changeName: "add-auth" });
|
|
690
|
+
|
|
691
|
+
assert.equal(status.nextRecommended, "archived");
|
|
692
|
+
assert.deepEqual(status.archived, {
|
|
693
|
+
path: join("openspec", "changes", "archive", "2026-03-04-add-auth"),
|
|
694
|
+
});
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
test("resolveSddStatus still blocks a change that never existed and has no archive entry", async () => {
|
|
698
|
+
const cwd = await workspace();
|
|
699
|
+
mkdirSync(join(cwd, "openspec", "changes"), { recursive: true });
|
|
700
|
+
mkdirSync(join(cwd, "openspec", "changes", "archive", "2026-01-01-unrelated"), { recursive: true });
|
|
701
|
+
|
|
702
|
+
const status = resolveSddStatus({ cwd, changeName: "ghost" });
|
|
703
|
+
|
|
704
|
+
assert.equal(status.archived, undefined);
|
|
705
|
+
assert.match(status.blockedReasons.join("\n"), /Active change not found/);
|
|
706
|
+
assert.notEqual(status.nextRecommended, "archived");
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
test("resolveSddStatus archive matching requires the exact change-name suffix", async () => {
|
|
710
|
+
const cwd = await workspace();
|
|
711
|
+
mkdirSync(join(cwd, "openspec", "changes"), { recursive: true });
|
|
712
|
+
mkdirSync(join(cwd, "openspec", "changes", "archive", "2026-01-01-foo-bar-baz"), { recursive: true });
|
|
713
|
+
|
|
714
|
+
const status = resolveSddStatus({ cwd, changeName: "foo-bar" });
|
|
715
|
+
|
|
716
|
+
assert.equal(status.archived, undefined);
|
|
717
|
+
assert.match(status.blockedReasons.join("\n"), /Active change not found/);
|
|
718
|
+
assert.notEqual(status.nextRecommended, "archived");
|
|
719
|
+
});
|
|
720
|
+
|
|
722
721
|
// Fix 4 render test — non-authoritative both status → dispatcher shows "both" not "Engram or none"
|
|
723
722
|
test("renderSddDispatcherMarkdown for non-authoritative both status shows artifact store 'both'", async () => {
|
|
724
723
|
const cwd = await workspace();
|
|
725
724
|
|
|
726
|
-
const status = resolveSddStatus({ cwd, artifactStore: "
|
|
725
|
+
const status = resolveSddStatus({ cwd, artifactStore: "hybrid", changeName: "fix-x" });
|
|
727
726
|
const markdown = renderSddDispatcherMarkdown(status);
|
|
728
727
|
|
|
729
|
-
assert.match(markdown, /artifact store:
|
|
728
|
+
assert.match(markdown, /artifact store: hybrid/);
|
|
730
729
|
assert.doesNotMatch(markdown, /Engram or none/);
|
|
731
730
|
assert.match(markdown, /resolve via Engram/i);
|
|
732
731
|
});
|
|
@@ -14,20 +14,18 @@ function readSkillName(dir: string): string | undefined {
|
|
|
14
14
|
const PREFIXED_NAMES: Record<string, string> = {
|
|
15
15
|
"branch-pr": "gentle-ai-branch-pr",
|
|
16
16
|
"chained-pr": "gentle-ai-chained-pr",
|
|
17
|
+
"cognitive-doc-design": "gentle-ai-cognitive-doc-design",
|
|
18
|
+
"comment-writer": "gentle-ai-comment-writer",
|
|
17
19
|
"issue-creation": "gentle-ai-issue-creation",
|
|
18
20
|
"judgment-day": "gentle-ai-judgment-day",
|
|
21
|
+
"rdd-defect-workflow": "gentle-ai-rdd-defect-workflow",
|
|
19
22
|
"skill-creator": "gentle-ai-skill-creator",
|
|
20
23
|
"skill-improver": "gentle-ai-skill-improver",
|
|
24
|
+
"skill-registry": "gentle-ai-skill-registry",
|
|
25
|
+
"work-unit-commits": "gentle-ai-work-unit-commits",
|
|
21
26
|
};
|
|
22
27
|
|
|
23
|
-
const UNPREFIXED_DIRS = [
|
|
24
|
-
"skill-registry",
|
|
25
|
-
"release",
|
|
26
|
-
"work-unit-commits",
|
|
27
|
-
"gentle-ai",
|
|
28
|
-
"comment-writer",
|
|
29
|
-
"cognitive-doc-design",
|
|
30
|
-
];
|
|
28
|
+
const UNPREFIXED_DIRS = ["gentle-ai", "release"];
|
|
31
29
|
|
|
32
30
|
for (const [dir, expectedName] of Object.entries(PREFIXED_NAMES)) {
|
|
33
31
|
test(`skills/${dir}/SKILL.md frontmatter name is prefixed`, () => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import { mkdirSync, readFileSync, symlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { mkdirSync, readFileSync, statSync, symlinkSync, utimesSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import test from "node:test";
|
|
@@ -267,3 +267,52 @@ test("orchestrator documents path injection protocol", () => {
|
|
|
267
267
|
assert.match(source, /paths-injected/);
|
|
268
268
|
assert.doesNotMatch(source, /Use matching compact rules based on code context and task intent/);
|
|
269
269
|
});
|
|
270
|
+
|
|
271
|
+
test("non-forced regeneration invalidates cache when skill bytes change but path, size, and mtime are restored", async () => {
|
|
272
|
+
const cwd = join(tmpdir(), `gentle-pi-fingerprint-${Date.now()}`);
|
|
273
|
+
const skillPath = join(cwd, "skills", "alpha", "SKILL.md");
|
|
274
|
+
mkdirSync(dirname(skillPath), { recursive: true });
|
|
275
|
+
|
|
276
|
+
const contentV1 =
|
|
277
|
+
'---\nname: alpha\ndescription: "Trigger: alpha skill. Variant one. Body A."\n---\n\n## Rules\n\n- Rule A.\n';
|
|
278
|
+
const contentV2 =
|
|
279
|
+
'---\nname: alpha\ndescription: "Trigger: alpha skill. Variant two. Body B."\n---\n\n## Rules\n\n- Rule B.\n';
|
|
280
|
+
assert.equal(
|
|
281
|
+
Buffer.byteLength(contentV1),
|
|
282
|
+
Buffer.byteLength(contentV2),
|
|
283
|
+
"test fixtures must have identical byte length",
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
const fixedMtimeSeconds = 1_000_000_000;
|
|
287
|
+
writeFileSync(skillPath, contentV1);
|
|
288
|
+
utimesSync(skillPath, fixedMtimeSeconds, fixedMtimeSeconds);
|
|
289
|
+
const beforeStat = statSync(skillPath);
|
|
290
|
+
const beforeMtimeMs = beforeStat.mtimeMs;
|
|
291
|
+
const beforeSize = beforeStat.size;
|
|
292
|
+
|
|
293
|
+
const first = await __testing.regenerateRegistry(cwd, false);
|
|
294
|
+
assert.equal(first.regenerated, true, "initial non-forced regeneration writes the registry");
|
|
295
|
+
assert.equal(first.reason, "fingerprint-changed");
|
|
296
|
+
|
|
297
|
+
const registryPath = join(cwd, ".atl", "skill-registry.md");
|
|
298
|
+
const firstRegistry = readFileSync(registryPath, "utf8");
|
|
299
|
+
assert.match(firstRegistry, /Variant one\. Body A\./);
|
|
300
|
+
|
|
301
|
+
writeFileSync(skillPath, contentV2);
|
|
302
|
+
utimesSync(skillPath, fixedMtimeSeconds, fixedMtimeSeconds);
|
|
303
|
+
const midStat = statSync(skillPath);
|
|
304
|
+
assert.equal(midStat.size, beforeSize, "byte size must be unchanged after rewrite");
|
|
305
|
+
assert.equal(midStat.mtimeMs, beforeMtimeMs, "mtime must be restored exactly");
|
|
306
|
+
|
|
307
|
+
const second = await __testing.regenerateRegistry(cwd, false);
|
|
308
|
+
assert.equal(
|
|
309
|
+
second.regenerated,
|
|
310
|
+
true,
|
|
311
|
+
"non-forced regeneration must invalidate cache when content bytes changed",
|
|
312
|
+
);
|
|
313
|
+
assert.equal(second.reason, "fingerprint-changed");
|
|
314
|
+
|
|
315
|
+
const secondRegistry = readFileSync(registryPath, "utf8");
|
|
316
|
+
assert.match(secondRegistry, /Variant two\. Body B\./);
|
|
317
|
+
assert.doesNotMatch(secondRegistry, /Variant one\. Body A\./);
|
|
318
|
+
});
|
|
@@ -4,9 +4,12 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import test from "node:test";
|
|
6
6
|
import {
|
|
7
|
+
gentleAiVersionPinMismatches,
|
|
7
8
|
reconcileContractsOnDisk,
|
|
8
9
|
reconcileGeneratedRuntimeSources,
|
|
9
10
|
} from "../scripts/verify-package-files.mjs";
|
|
11
|
+
import { INSTALLER_VERSION, RELEASE_BASE_URL, GENTLE_AI_WINDOWS_SOURCE_TAG } from "../scripts/gentle-ai-installer.mjs";
|
|
12
|
+
import { GENTLE_AI_VERSION } from "../lib/gentle-ai-binary.ts";
|
|
10
13
|
|
|
11
14
|
function makeFixtureRoot(): string {
|
|
12
15
|
return mkdtempSync(join(tmpdir(), "gentle-pi-verify-package-files-"));
|
|
@@ -49,6 +52,22 @@ test("contracts/ walk fails on a contractHashes entry that is missing from disk"
|
|
|
49
52
|
}
|
|
50
53
|
});
|
|
51
54
|
|
|
55
|
+
test("contracts/ walk excludes the provider contract mirror subtree, whose bytes are pinned by its own lock", () => {
|
|
56
|
+
const fixtureRoot = makeFixtureRoot();
|
|
57
|
+
try {
|
|
58
|
+
mkdirSync(join(fixtureRoot, "contracts/review-provider-contract-mirror/v1.1.0/bundle"), { recursive: true });
|
|
59
|
+
writeFileSync(join(fixtureRoot, "contracts/review-provider-contract-mirror/provider-contract.lock.json"), "{}\n");
|
|
60
|
+
writeFileSync(join(fixtureRoot, "contracts/review-provider-contract-mirror/v1.1.0/bundle/README.md"), "mirror\n");
|
|
61
|
+
|
|
62
|
+
const { unlistedOnDisk, listedButMissing } = reconcileContractsOnDisk(fixtureRoot, {});
|
|
63
|
+
|
|
64
|
+
assert.deepEqual(unlistedOnDisk, []);
|
|
65
|
+
assert.deepEqual(listedButMissing, []);
|
|
66
|
+
} finally {
|
|
67
|
+
rmSync(fixtureRoot, { recursive: true, force: true });
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
52
71
|
test("sources <-> runtime/*.mjs walk fails when a generated runtime file is absent from the generator's sources array", () => {
|
|
53
72
|
const fixtureRoot = makeFixtureRoot();
|
|
54
73
|
try {
|
|
@@ -91,6 +110,49 @@ test("sources <-> runtime/*.mjs walk fails when a sources entry is absent from r
|
|
|
91
110
|
}
|
|
92
111
|
});
|
|
93
112
|
|
|
113
|
+
// Regression test for the documented incident in scripts/install-gentle-ai.mjs:
|
|
114
|
+
// the installer once reported installing v2.1.11 while writing v2.2.0 to disk
|
|
115
|
+
// because two hardcoded version copies had drifted apart. The textual
|
|
116
|
+
// `.includes(...)` grep this replaces could not have caught this, because it
|
|
117
|
+
// only verifies a string appears in a file, not that two values agree.
|
|
118
|
+
test("Gentle AI version pin mismatch is reported with a specific message when a derived location disagrees with the authoritative constant", () => {
|
|
119
|
+
const mismatches = gentleAiVersionPinMismatches({
|
|
120
|
+
installerVersion: "2.2.3",
|
|
121
|
+
releaseBaseUrl: "https://github.com/Gentleman-Programming/gentle-ai/releases/download/v2.2.3/",
|
|
122
|
+
windowsSourceTag: "v2.2.3",
|
|
123
|
+
libGentleAiVersion: "2.2.0",
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
assert.deepEqual(mismatches, [
|
|
127
|
+
'lib/gentle-ai-binary.ts GENTLE_AI_VERSION ("2.2.0") does not match the authoritative scripts/gentle-ai-installer.mjs INSTALLER_VERSION ("2.2.3")',
|
|
128
|
+
]);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("Gentle AI version pin mismatch also flags a drifted release URL and a drifted Windows source tag", () => {
|
|
132
|
+
const mismatches = gentleAiVersionPinMismatches({
|
|
133
|
+
installerVersion: "2.2.3",
|
|
134
|
+
releaseBaseUrl: "https://github.com/Gentleman-Programming/gentle-ai/releases/download/v2.2.0/",
|
|
135
|
+
windowsSourceTag: "v2.1.11",
|
|
136
|
+
libGentleAiVersion: "2.2.3",
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
assert.deepEqual(mismatches, [
|
|
140
|
+
'RELEASE_BASE_URL ("https://github.com/Gentleman-Programming/gentle-ai/releases/download/v2.2.0/") does not pin the authoritative v2.2.3',
|
|
141
|
+
'GENTLE_AI_WINDOWS_SOURCE_TAG ("v2.1.11") does not match the authoritative v2.2.3',
|
|
142
|
+
]);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("Gentle AI version pin agrees across the installer version, the release URL, the Windows source tag, and lib/gentle-ai-binary.ts", () => {
|
|
146
|
+
const mismatches = gentleAiVersionPinMismatches({
|
|
147
|
+
installerVersion: INSTALLER_VERSION,
|
|
148
|
+
releaseBaseUrl: RELEASE_BASE_URL,
|
|
149
|
+
windowsSourceTag: GENTLE_AI_WINDOWS_SOURCE_TAG,
|
|
150
|
+
libGentleAiVersion: GENTLE_AI_VERSION,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
assert.deepEqual(mismatches, []);
|
|
154
|
+
});
|
|
155
|
+
|
|
94
156
|
test("both walks report no drift when sources, runtime/*.mjs, requiredPaths, and contractHashes fully agree", () => {
|
|
95
157
|
const fixtureRoot = makeFixtureRoot();
|
|
96
158
|
try {
|