safeword 0.72.0 → 0.73.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/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/codex-plugin/hooks.json +5 -5
- package/codex-plugin/skills/bdd/references/SCENARIOS.md +6 -6
- package/codex-plugin/skills/closeout/SKILL.md +20 -7
- package/dist/{chunk-PGUVVBIN.js → chunk-7BLT7HXQ.js} +1 -1
- package/dist/chunk-7BLT7HXQ.js.map +1 -0
- package/dist/{chunk-7NZ26QJP.js → chunk-7D2H4IZ5.js} +3 -3
- package/dist/chunk-FTCNAEAL.js +117 -0
- package/dist/chunk-FTCNAEAL.js.map +1 -0
- package/dist/{chunk-KWBGQ2YZ.js → chunk-HKFPZSXT.js} +2 -2
- package/dist/{chunk-F4CA33GP.js → chunk-IWU7G6G7.js} +19 -15
- package/dist/chunk-IWU7G6G7.js.map +1 -0
- package/dist/chunk-KNKA4KSG.js +632 -0
- package/dist/chunk-KNKA4KSG.js.map +1 -0
- package/dist/chunk-MF7ZXVYN.js +318 -0
- package/dist/chunk-MF7ZXVYN.js.map +1 -0
- package/dist/{migrate-codex-plugin-CPLYYCGP.js → chunk-NDY73DUG.js} +430 -146
- package/dist/chunk-NDY73DUG.js.map +1 -0
- package/dist/{chunk-QPWX5JXI.js → chunk-QOGMLRPC.js} +51 -5
- package/dist/chunk-QOGMLRPC.js.map +1 -0
- package/dist/{chunk-XIGSW56Z.js → chunk-VB7VIQRO.js} +118 -20
- package/dist/chunk-VB7VIQRO.js.map +1 -0
- package/dist/{chunk-4HWB6QFG.js → chunk-WWF42RYW.js} +2 -2
- package/dist/{chunk-I5QEPYVK.js → chunk-ZERKNRHZ.js} +8 -122
- package/dist/chunk-ZERKNRHZ.js.map +1 -0
- package/dist/{cleanup-STJVSD7S.js → cleanup-XX2KWNNQ.js} +34 -23
- package/dist/cleanup-XX2KWNNQ.js.map +1 -0
- package/dist/cli.js +65 -23
- package/dist/cli.js.map +1 -1
- package/dist/codex-bootstrap-VOQXVQ6F.js +124 -0
- package/dist/codex-bootstrap-VOQXVQ6F.js.map +1 -0
- package/dist/{codex-hook-HBEPMPEK.js → codex-hook-DZ4HY5V5.js} +20 -13
- package/dist/codex-hook-DZ4HY5V5.js.map +1 -0
- package/dist/{converge-setup-6WS2RMSG.js → converge-setup-XVQEDGLV.js} +75 -25
- package/dist/converge-setup-XVQEDGLV.js.map +1 -0
- package/dist/{finalization-3YYUBUTJ.js → finalization-ES4OERZA.js} +3 -3
- package/dist/migrate-codex-plugin-YCE6PINW.js +38 -0
- package/dist/{plan-RRN2ESJ7.js → plan-AX35QFPS.js} +5 -5
- package/dist/{profile-DM4UNHMN.js → profile-SNTDKMIH.js} +10 -4
- package/dist/{remove-GNAT5APA.js → remove-6BPYCP7W.js} +5 -5
- package/dist/{retro-XO7CALV4.js → retro-4B2WJD2S.js} +109 -28
- package/dist/retro-4B2WJD2S.js.map +1 -0
- package/dist/{retro-extract-E5F5A35R.js → retro-extract-72SKMRZE.js} +10 -2
- package/dist/{status-5DDTQIJU.js → status-KQTI2O3O.js} +7 -5
- package/dist/status-KQTI2O3O.js.map +1 -0
- package/dist/{status-ILKQIXSD.js → status-Q4FPQNSV.js} +10 -8
- package/dist/{status-ILKQIXSD.js.map → status-Q4FPQNSV.js.map} +1 -1
- package/package.json +1 -1
- package/templates/SAFEWORD.md +1 -1
- package/templates/hooks/lib/closeout-binding.ts +29 -6
- package/templates/hooks/lib/quality.ts +261 -15
- package/templates/hooks/lib/retro-extract.ts +192 -33
- package/templates/hooks/record-skill-invocation.ts +3 -1
- package/templates/hooks/session-reply-format.ts +27 -0
- package/templates/hooks/stop-quality.ts +9 -1
- package/templates/scripts/closeout-cleanup.ts +229 -50
- package/templates/skills/bdd/SCENARIOS.md +6 -6
- package/templates/skills/closeout/SKILL.md +20 -7
- package/dist/chunk-2MQTOSGG.js +0 -312
- package/dist/chunk-2MQTOSGG.js.map +0 -1
- package/dist/chunk-F4CA33GP.js.map +0 -1
- package/dist/chunk-I5QEPYVK.js.map +0 -1
- package/dist/chunk-PGUVVBIN.js.map +0 -1
- package/dist/chunk-QPWX5JXI.js.map +0 -1
- package/dist/chunk-XIGSW56Z.js.map +0 -1
- package/dist/chunk-Y6LUZKTN.js +0 -144
- package/dist/chunk-Y6LUZKTN.js.map +0 -1
- package/dist/cleanup-STJVSD7S.js.map +0 -1
- package/dist/codex-hook-HBEPMPEK.js.map +0 -1
- package/dist/converge-setup-6WS2RMSG.js.map +0 -1
- package/dist/migrate-codex-plugin-CPLYYCGP.js.map +0 -1
- package/dist/retro-XO7CALV4.js.map +0 -1
- /package/dist/{chunk-7NZ26QJP.js.map → chunk-7D2H4IZ5.js.map} +0 -0
- /package/dist/{chunk-KWBGQ2YZ.js.map → chunk-HKFPZSXT.js.map} +0 -0
- /package/dist/{chunk-4HWB6QFG.js.map → chunk-WWF42RYW.js.map} +0 -0
- /package/dist/{finalization-3YYUBUTJ.js.map → finalization-ES4OERZA.js.map} +0 -0
- /package/dist/{profile-DM4UNHMN.js.map → migrate-codex-plugin-YCE6PINW.js.map} +0 -0
- /package/dist/{plan-RRN2ESJ7.js.map → plan-AX35QFPS.js.map} +0 -0
- /package/dist/{retro-extract-E5F5A35R.js.map → profile-SNTDKMIH.js.map} +0 -0
- /package/dist/{remove-GNAT5APA.js.map → remove-6BPYCP7W.js.map} +0 -0
- /package/dist/{status-5DDTQIJU.js.map → retro-extract-72SKMRZE.js.map} +0 -0
package/codex-plugin/hooks.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"hooks": [
|
|
7
7
|
{
|
|
8
8
|
"type": "command",
|
|
9
|
-
"command": "bunx --bun safeword@0.
|
|
9
|
+
"command": "bunx --bun safeword@0.73.0 hook codex session-start --plugin-hook",
|
|
10
10
|
"timeout": 120,
|
|
11
11
|
"statusMessage": "Loading Safe Word standing instructions"
|
|
12
12
|
}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"hooks": [
|
|
20
20
|
{
|
|
21
21
|
"type": "command",
|
|
22
|
-
"command": "bunx --bun safeword@0.
|
|
22
|
+
"command": "bunx --bun safeword@0.73.0 hook codex pre-tool-use --plugin-hook",
|
|
23
23
|
"timeout": 30,
|
|
24
24
|
"statusMessage": "Checking Safe Word edit gates"
|
|
25
25
|
}
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"hooks": [
|
|
33
33
|
{
|
|
34
34
|
"type": "command",
|
|
35
|
-
"command": "bunx --bun safeword@0.
|
|
35
|
+
"command": "bunx --bun safeword@0.73.0 hook codex post-tool-use --plugin-hook",
|
|
36
36
|
"timeout": 30,
|
|
37
37
|
"statusMessage": "Surfacing Safe Word post-tool context"
|
|
38
38
|
}
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"hooks": [
|
|
46
46
|
{
|
|
47
47
|
"type": "command",
|
|
48
|
-
"command": "bunx --bun safeword@0.
|
|
48
|
+
"command": "bunx --bun safeword@0.73.0 hook codex user-prompt-submit --plugin-hook",
|
|
49
49
|
"timeout": 30,
|
|
50
50
|
"statusMessage": "Checking queued Safe Word prompt context"
|
|
51
51
|
}
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"hooks": [
|
|
59
59
|
{
|
|
60
60
|
"type": "command",
|
|
61
|
-
"command": "bunx --bun safeword@0.
|
|
61
|
+
"command": "bunx --bun safeword@0.73.0 hook codex stop --plugin-hook",
|
|
62
62
|
"timeout": 600,
|
|
63
63
|
"statusMessage": "Checking Safe Word stop continuation"
|
|
64
64
|
}
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
|
|
19
19
|
Save the dimension table to `dimensions.md` in the ticket folder before writing test-definitions.md (the pre-tool hook enforces this for features). For tiny features with one obvious behavioral dimension and no partitioning to enumerate, dimensions.md may instead be a single line `skip: <non-empty reason>`.
|
|
20
20
|
|
|
21
|
-
###
|
|
21
|
+
### Are the scenarios complete?
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
Ask the user: **Do these scenarios fully describe the intended behavior and important boundaries, or is anything missing?** If their feedback adds scenarios, revise the set and ask again. When they confirm nothing is missing, proceed to scenario-gate.
|
|
24
24
|
|
|
25
25
|
### Concrete example
|
|
26
26
|
|
|
@@ -55,7 +55,7 @@ Each propose-and-converge turn either surfaces new scenarios or doesn't. When a
|
|
|
55
55
|
|
|
56
56
|
**User:** "Looks good, proceed."
|
|
57
57
|
|
|
58
|
-
**Result:**
|
|
58
|
+
**Result:** The user confirms the intended behavior and important boundaries are fully described → proceed to scenario-gate.
|
|
59
59
|
|
|
60
60
|
### Two formats: discovery vs saved
|
|
61
61
|
|
|
@@ -198,11 +198,11 @@ delivery retries on exponential backoff`). IDs are 1-indexed per job and
|
|
|
198
198
|
|
|
199
199
|
**Entry:** Agent enters `scenario-gate` phase.
|
|
200
200
|
|
|
201
|
-
Run the **`$safeword:review-spec`** skill — it is the gate procedure (vacuous-pass, AODI, determinism risks, adversarial pass + negative-case, cross-cutting checks, and the findings format). It reads the active ticket's `.feature` source when present, using `test-definitions.md` only as the R/G/R ledger, reports findings, and is re-invokable standalone after scenario edits. Apply its findings, then complete the
|
|
201
|
+
Run the **`$safeword:review-spec`** skill — it is the gate procedure (vacuous-pass, AODI, determinism risks, adversarial pass + negative-case, cross-cutting checks, and the findings format). It reads the active ticket's `.feature` source when present, using `test-definitions.md` only as the R/G/R ledger, reports findings, and is re-invokable standalone after scenario edits. Apply its findings, then complete the plain-language completeness check and exit below.
|
|
202
202
|
|
|
203
|
-
###
|
|
203
|
+
### Are the reviewed scenarios complete?
|
|
204
204
|
|
|
205
|
-
If the adversarial pass
|
|
205
|
+
Ask the user: **Do these scenarios now fully cover the intended behavior and important boundaries, or is anything still missing?** If the adversarial pass or user feedback produced new scenarios, loop back to define-behavior. When nothing is missing, the quality gate is complete.
|
|
206
206
|
|
|
207
207
|
### Scenario Gate Exit
|
|
208
208
|
|
|
@@ -27,6 +27,9 @@ Collect and report every blocker. Missing, stale, failing, pending, unknown, or
|
|
|
27
27
|
ambiguous evidence means **no merge or cleanup**. A merge command's exit status
|
|
28
28
|
never proves that the pull request is merged.
|
|
29
29
|
|
|
30
|
+
Dependency audit is part of this delivery-time verification boundary. Resolve
|
|
31
|
+
its failures before merge while the pull request head can still be changed.
|
|
32
|
+
|
|
30
33
|
## 2. Respect merge authority
|
|
31
34
|
|
|
32
35
|
Invocation alone grants no merge authority. Read authority only from the current user request;
|
|
@@ -63,13 +66,20 @@ complete only after proving it was the exact planned target. If the pull request
|
|
|
63
66
|
is merged, its retrospective is complete, and its exact branch and worktree are
|
|
64
67
|
already absent, report that the session is already closed.
|
|
65
68
|
|
|
69
|
+
The guard records a private, atomic verification receipt in Git's shared common
|
|
70
|
+
directory only after every verification lane passes on a clean exact PR head.
|
|
71
|
+
For 24 hours, that receipt can prove the immutable head when an interrupted
|
|
72
|
+
cleanup must resume from a surviving worktree after the topic worktree is gone.
|
|
73
|
+
A missing, stale, malformed, dirty-state, or wrong-head receipt blocks cleanup.
|
|
74
|
+
|
|
66
75
|
## 4. Complete the current session's retrospective
|
|
67
76
|
|
|
68
77
|
After merge is independently confirmed, invoke the cleanup guard in preview
|
|
69
78
|
mode. Its host hook supplies a short-lived, single-consumer binding to this exact
|
|
70
|
-
session (and Cursor transcript).
|
|
71
|
-
|
|
72
|
-
|
|
79
|
+
session (and Cursor transcript). Codex Desktop may instead supply its authenticated
|
|
80
|
+
current `CODEX_THREAD_ID`, consistent with SafeWord's other Codex identity bridges.
|
|
81
|
+
A missing or expired binding or identity fails closed; there is no newest-session
|
|
82
|
+
fallback and callers cannot nominate another receipt, session, transcript, or spool.
|
|
73
83
|
|
|
74
84
|
The guard runs `safeword retro run --json` itself and accepts only a
|
|
75
85
|
successful result whose `data.agent_filing_needed` is `false` and whose derived
|
|
@@ -89,10 +99,13 @@ Run the guard from the delivery worktree; preview is the default:
|
|
|
89
99
|
bun .safeword/scripts/closeout-cleanup.ts --pr PR_NUMBER
|
|
90
100
|
```
|
|
91
101
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
102
|
+
At the exact delivery head, the post-merge preview reruns the project's
|
|
103
|
+
verification, build, typecheck, and BDD plans. It does not rerun dependency audit:
|
|
104
|
+
that changing intelligence is enforced at the delivery-time, pre-merge boundary
|
|
105
|
+
and cannot repair an immutable merged head. After that worktree is gone, preview
|
|
106
|
+
requires its fresh clean-head receipt instead. It binds the resulting repository
|
|
107
|
+
state and exact PR identity to `PLAN_DIGEST`. Report the complete operation list
|
|
108
|
+
and all blockers. Do not apply a blocked plan.
|
|
96
109
|
|
|
97
110
|
With the user's cleanup intent already established by invoking closeout, apply
|
|
98
111
|
only the unchanged preview:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/codex-plugin/migration-error.ts"],"sourcesContent":["export type CodexMigrationErrorCode =\n | 'PLUGIN_ENABLEMENT_UNKNOWN'\n | 'PLUGIN_ENABLEMENT_FAILED'\n | 'PLUGIN_MARKETPLACE_FAILED'\n | 'PLUGIN_PROFILE_BUSY'\n | 'PLUGIN_NEWER_PIN_PRESERVED'\n | 'AMBIGUOUS_LEGACY_CONFIG'\n | 'UNSAFE_MIGRATION_PATH'\n | 'BACKUP_EXISTS'\n | 'ROLLBACK_FAILED'\n | 'RECOVERY_CONFLICT'\n | 'PLAN_STALE'\n | 'FINALIZATION_PROOF_REQUIRED';\n\nexport class CodexMigrationError extends Error {\n readonly code: CodexMigrationErrorCode;\n\n constructor(code: CodexMigrationErrorCode, message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = 'CodexMigrationError';\n this.code = code;\n }\n}\n"],"mappings":";AAcO,IAAM,sBAAN,cAAkC,MAAM;AAAA,EACpC;AAAA,EAET,YAAY,MAA+B,SAAiB,SAAwB;AAClF,UAAM,SAAS,OAAO;AACtB,SAAK,OAAO;AACZ,SAAK,OAAO;AAAA,EACd;AACF;","names":[]}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createProjectContext,
|
|
3
3
|
reconcile
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-HKFPZSXT.js";
|
|
5
5
|
import {
|
|
6
6
|
SAFEWORD_SCHEMA
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-IWU7G6G7.js";
|
|
8
8
|
import {
|
|
9
9
|
createPlan
|
|
10
10
|
} from "./chunk-RLH5PLSD.js";
|
|
@@ -99,4 +99,4 @@ export {
|
|
|
99
99
|
createReconciliationPlan,
|
|
100
100
|
applyReconciliation
|
|
101
101
|
};
|
|
102
|
-
//# sourceMappingURL=chunk-
|
|
102
|
+
//# sourceMappingURL=chunk-7D2H4IZ5.js.map
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// src/utils/version.ts
|
|
2
|
+
function onlyAsciiDigits(value) {
|
|
3
|
+
if (value.length === 0) return false;
|
|
4
|
+
for (const character of value) {
|
|
5
|
+
if (character < "0" || character > "9") return false;
|
|
6
|
+
}
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
function validSemverIdentifier(value) {
|
|
10
|
+
if (value.length === 0) return false;
|
|
11
|
+
for (const character of value) {
|
|
12
|
+
const valid = character >= "0" && character <= "9" || character >= "A" && character <= "Z" || character >= "a" && character <= "z" || character === "-";
|
|
13
|
+
if (!valid) return false;
|
|
14
|
+
}
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
function validSemverIdentifiers(value) {
|
|
18
|
+
return value.split(".").every((identifier) => validSemverIdentifier(identifier));
|
|
19
|
+
}
|
|
20
|
+
function hasForbiddenLeadingZero(value) {
|
|
21
|
+
return value.length > 1 && value.startsWith("0");
|
|
22
|
+
}
|
|
23
|
+
function parseCore(value) {
|
|
24
|
+
const parts = value.split(".");
|
|
25
|
+
if (parts.length !== 3 || parts.some((part) => !onlyAsciiDigits(part) || hasForbiddenLeadingZero(part))) {
|
|
26
|
+
return void 0;
|
|
27
|
+
}
|
|
28
|
+
return parts;
|
|
29
|
+
}
|
|
30
|
+
function parseIdentifiers(value, forbidNumericLeadingZeros) {
|
|
31
|
+
if (!validSemverIdentifiers(value)) return void 0;
|
|
32
|
+
const identifiers = value.split(".");
|
|
33
|
+
if (forbidNumericLeadingZeros && identifiers.some(
|
|
34
|
+
(identifier) => onlyAsciiDigits(identifier) && hasForbiddenLeadingZero(identifier)
|
|
35
|
+
)) {
|
|
36
|
+
return void 0;
|
|
37
|
+
}
|
|
38
|
+
return identifiers;
|
|
39
|
+
}
|
|
40
|
+
function parseSemver(version) {
|
|
41
|
+
const buildParts = version.split("+");
|
|
42
|
+
if (buildParts.length > 2) return void 0;
|
|
43
|
+
const withoutBuild = buildParts[0] ?? "";
|
|
44
|
+
const build = buildParts[1];
|
|
45
|
+
if (build !== void 0 && parseIdentifiers(build, false) === void 0) return void 0;
|
|
46
|
+
const prereleaseSeparator = withoutBuild.indexOf("-");
|
|
47
|
+
const coreText = prereleaseSeparator === -1 ? withoutBuild : withoutBuild.slice(0, prereleaseSeparator);
|
|
48
|
+
const prereleaseText = prereleaseSeparator === -1 ? void 0 : withoutBuild.slice(prereleaseSeparator + 1);
|
|
49
|
+
const core = parseCore(coreText);
|
|
50
|
+
if (core === void 0) return void 0;
|
|
51
|
+
const prerelease = prereleaseText === void 0 ? [] : parseIdentifiers(prereleaseText, true);
|
|
52
|
+
return prerelease === void 0 ? void 0 : { core, prerelease };
|
|
53
|
+
}
|
|
54
|
+
function compareNumeric(left, right) {
|
|
55
|
+
const leftValue = BigInt(left);
|
|
56
|
+
const rightValue = BigInt(right);
|
|
57
|
+
if (leftValue < rightValue) return -1;
|
|
58
|
+
if (leftValue > rightValue) return 1;
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
function comparePrereleaseIdentifier(left, right) {
|
|
62
|
+
if (left === right) return 0;
|
|
63
|
+
const leftNumeric = onlyAsciiDigits(left);
|
|
64
|
+
const rightNumeric = onlyAsciiDigits(right);
|
|
65
|
+
if (leftNumeric && rightNumeric) return compareNumeric(left, right);
|
|
66
|
+
if (leftNumeric) return -1;
|
|
67
|
+
if (rightNumeric) return 1;
|
|
68
|
+
return left < right ? -1 : 1;
|
|
69
|
+
}
|
|
70
|
+
function comparePrerelease(left, right) {
|
|
71
|
+
if (left.length === 0 && right.length === 0) return 0;
|
|
72
|
+
if (left.length === 0) return 1;
|
|
73
|
+
if (right.length === 0) return -1;
|
|
74
|
+
for (let index = 0; index < Math.max(left.length, right.length); index++) {
|
|
75
|
+
const leftIdentifier = left[index];
|
|
76
|
+
if (leftIdentifier === void 0) return -1;
|
|
77
|
+
const rightIdentifier = right[index];
|
|
78
|
+
if (rightIdentifier === void 0) return 1;
|
|
79
|
+
const comparison = comparePrereleaseIdentifier(leftIdentifier, rightIdentifier);
|
|
80
|
+
if (comparison !== 0) return comparison;
|
|
81
|
+
}
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
function compareCore(left, right) {
|
|
85
|
+
for (let index = 0; index < 3; index++) {
|
|
86
|
+
const comparison = compareNumeric(left[index] ?? "0", right[index] ?? "0");
|
|
87
|
+
if (comparison !== 0) return comparison;
|
|
88
|
+
}
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
function compareLegacyVersions(left, right) {
|
|
92
|
+
const leftParts = left.split(".").map(Number);
|
|
93
|
+
const rightParts = right.split(".").map(Number);
|
|
94
|
+
for (let index = 0; index < 3; index++) {
|
|
95
|
+
const leftValue = leftParts[index] ?? 0;
|
|
96
|
+
const rightValue = rightParts[index] ?? 0;
|
|
97
|
+
if (leftValue < rightValue) return -1;
|
|
98
|
+
if (leftValue > rightValue) return 1;
|
|
99
|
+
}
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
function compareVersions(a, b) {
|
|
103
|
+
const parsedA = parseSemver(a);
|
|
104
|
+
const parsedB = parseSemver(b);
|
|
105
|
+
if (parsedA === void 0 || parsedB === void 0) return compareLegacyVersions(a, b);
|
|
106
|
+
const coreComparison = compareCore(parsedA.core, parsedB.core);
|
|
107
|
+
return coreComparison === 0 ? comparePrerelease(parsedA.prerelease, parsedB.prerelease) : coreComparison;
|
|
108
|
+
}
|
|
109
|
+
function isSafePackageVersion(version) {
|
|
110
|
+
return parseSemver(version) !== void 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export {
|
|
114
|
+
compareVersions,
|
|
115
|
+
isSafePackageVersion
|
|
116
|
+
};
|
|
117
|
+
//# sourceMappingURL=chunk-FTCNAEAL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/version.ts"],"sourcesContent":["/**\n * Version comparison utilities\n */\n\nfunction onlyAsciiDigits(value: string): boolean {\n if (value.length === 0) return false;\n for (const character of value) {\n if (character < '0' || character > '9') return false;\n }\n return true;\n}\n\nfunction validSemverIdentifier(value: string): boolean {\n if (value.length === 0) return false;\n for (const character of value) {\n const valid =\n (character >= '0' && character <= '9') ||\n (character >= 'A' && character <= 'Z') ||\n (character >= 'a' && character <= 'z') ||\n character === '-';\n if (!valid) return false;\n }\n return true;\n}\n\nfunction validSemverIdentifiers(value: string): boolean {\n return value.split('.').every(identifier => validSemverIdentifier(identifier));\n}\n\ninterface ParsedSemver {\n readonly core: readonly [string, string, string];\n readonly prerelease: readonly string[];\n}\n\ntype Comparison = -1 | 0 | 1;\n\nfunction hasForbiddenLeadingZero(value: string): boolean {\n return value.length > 1 && value.startsWith('0');\n}\n\nfunction parseCore(value: string): ParsedSemver['core'] | undefined {\n const parts = value.split('.');\n if (\n parts.length !== 3 ||\n parts.some(part => !onlyAsciiDigits(part) || hasForbiddenLeadingZero(part))\n ) {\n return undefined;\n }\n return parts as [string, string, string];\n}\n\nfunction parseIdentifiers(\n value: string,\n forbidNumericLeadingZeros: boolean,\n): readonly string[] | undefined {\n if (!validSemverIdentifiers(value)) return undefined;\n const identifiers = value.split('.');\n if (\n forbidNumericLeadingZeros &&\n identifiers.some(\n identifier => onlyAsciiDigits(identifier) && hasForbiddenLeadingZero(identifier),\n )\n ) {\n return undefined;\n }\n return identifiers;\n}\n\nfunction parseSemver(version: string): ParsedSemver | undefined {\n const buildParts = version.split('+');\n if (buildParts.length > 2) return undefined;\n const withoutBuild = buildParts[0] ?? '';\n const build = buildParts[1];\n if (build !== undefined && parseIdentifiers(build, false) === undefined) return undefined;\n\n const prereleaseSeparator = withoutBuild.indexOf('-');\n const coreText =\n prereleaseSeparator === -1 ? withoutBuild : withoutBuild.slice(0, prereleaseSeparator);\n const prereleaseText =\n prereleaseSeparator === -1 ? undefined : withoutBuild.slice(prereleaseSeparator + 1);\n const core = parseCore(coreText);\n if (core === undefined) return undefined;\n const prerelease = prereleaseText === undefined ? [] : parseIdentifiers(prereleaseText, true);\n return prerelease === undefined ? undefined : { core, prerelease };\n}\n\nfunction compareNumeric(left: string, right: string): Comparison {\n const leftValue = BigInt(left);\n const rightValue = BigInt(right);\n if (leftValue < rightValue) return -1;\n if (leftValue > rightValue) return 1;\n return 0;\n}\n\nfunction comparePrereleaseIdentifier(left: string, right: string): Comparison {\n if (left === right) return 0;\n const leftNumeric = onlyAsciiDigits(left);\n const rightNumeric = onlyAsciiDigits(right);\n if (leftNumeric && rightNumeric) return compareNumeric(left, right);\n if (leftNumeric) return -1;\n if (rightNumeric) return 1;\n return left < right ? -1 : 1;\n}\n\nfunction comparePrerelease(left: readonly string[], right: readonly string[]): Comparison {\n if (left.length === 0 && right.length === 0) return 0;\n if (left.length === 0) return 1;\n if (right.length === 0) return -1;\n\n for (let index = 0; index < Math.max(left.length, right.length); index++) {\n const leftIdentifier = left[index];\n if (leftIdentifier === undefined) return -1;\n const rightIdentifier = right[index];\n if (rightIdentifier === undefined) return 1;\n const comparison = comparePrereleaseIdentifier(leftIdentifier, rightIdentifier);\n if (comparison !== 0) return comparison;\n }\n return 0;\n}\n\nfunction compareCore(left: ParsedSemver['core'], right: ParsedSemver['core']): Comparison {\n for (let index = 0; index < 3; index++) {\n const comparison = compareNumeric(left[index] ?? '0', right[index] ?? '0');\n if (comparison !== 0) return comparison;\n }\n return 0;\n}\n\nfunction compareLegacyVersions(left: string, right: string): Comparison {\n const leftParts = left.split('.').map(Number);\n const rightParts = right.split('.').map(Number);\n for (let index = 0; index < 3; index++) {\n const leftValue = leftParts[index] ?? 0;\n const rightValue = rightParts[index] ?? 0;\n if (leftValue < rightValue) return -1;\n if (leftValue > rightValue) return 1;\n }\n return 0;\n}\n\n/**\n * Compare SemVer versions. Legacy non-SemVer inputs retain numeric-core comparison\n * so downgrade protection remains conservative for older project markers.\n *\n * Safety gates must validate both inputs with `isSafePackageVersion` before calling\n * this function: the compatibility fallback cannot distinguish equal numeric cores\n * from wholly unparseable input.\n * @param a\n * @param b\n * @returns -1 if a < b, 0 if a == b, 1 if a > b\n */\nexport function compareVersions(a: string, b: string): Comparison {\n const parsedA = parseSemver(a);\n const parsedB = parseSemver(b);\n if (parsedA === undefined || parsedB === undefined) return compareLegacyVersions(a, b);\n const coreComparison = compareCore(parsedA.core, parsedB.core);\n return coreComparison === 0\n ? comparePrerelease(parsedA.prerelease, parsedB.prerelease)\n : coreComparison;\n}\n\n/** Accept a SemVer-shaped package version before interpolating it into a shell command. */\nexport function isSafePackageVersion(version: string): boolean {\n return parseSemver(version) !== undefined;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-ZESGPJCY.js";
|
|
5
5
|
import {
|
|
6
6
|
setupPythonTooling
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-IWU7G6G7.js";
|
|
8
8
|
import {
|
|
9
9
|
SAFEWORD_PRIMARY_LINT_SCRIPT,
|
|
10
10
|
detect
|
|
@@ -1544,4 +1544,4 @@ export {
|
|
|
1544
1544
|
LEFTHOOK_CONFIGS,
|
|
1545
1545
|
createProjectContext
|
|
1546
1546
|
};
|
|
1547
|
-
//# sourceMappingURL=chunk-
|
|
1547
|
+
//# sourceMappingURL=chunk-HKFPZSXT.js.map
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CODEX_MIGRATION_SCHEMA
|
|
3
|
-
} from "./chunk-O5JEWG3M.js";
|
|
4
1
|
import {
|
|
5
2
|
MCP_SERVERS
|
|
6
3
|
} from "./chunk-GUIZH3NI.js";
|
|
4
|
+
import {
|
|
5
|
+
CODEX_MIGRATION_SCHEMA
|
|
6
|
+
} from "./chunk-O5JEWG3M.js";
|
|
7
7
|
import {
|
|
8
8
|
warn
|
|
9
9
|
} from "./chunk-DAFOEHJA.js";
|
|
@@ -1454,12 +1454,12 @@ var SETTINGS_HOOKS = {
|
|
|
1454
1454
|
hook(`bun ${HOOKS_DIR}/session-dependency-readiness.ts`),
|
|
1455
1455
|
asyncRewakeHook(`bun ${HOOKS_DIR}/session-auto-upgrade.ts`),
|
|
1456
1456
|
hook(`bun ${HOOKS_DIR}/session-safeword-context.ts --agent=claude`),
|
|
1457
|
+
hook(`bun ${HOOKS_DIR}/session-reply-format.ts --agent=claude`),
|
|
1457
1458
|
hook(`bun ${HOOKS_DIR}/session-version.ts`),
|
|
1458
1459
|
hook(`bun ${HOOKS_DIR}/session-lint-check.ts`),
|
|
1459
1460
|
hook(`bun ${HOOKS_DIR}/session-architecture-heal.ts`),
|
|
1460
1461
|
hook(`bun ${HOOKS_DIR}/session-author-model.ts`),
|
|
1461
1462
|
hook(`bun ${HOOKS_DIR}/session-start-reentry.ts`),
|
|
1462
|
-
matchedHook("compact", `bun ${HOOKS_DIR}/session-safeword-context.ts --agent=claude`),
|
|
1463
1463
|
matchedHook("compact", `bun ${HOOKS_DIR}/session-compact-context.ts`)
|
|
1464
1464
|
],
|
|
1465
1465
|
UserPromptSubmit: [
|
|
@@ -2606,8 +2606,14 @@ var SAFEWORD_SCHEMA = {
|
|
|
2606
2606
|
".safeword/hooks/session-safeword-context.ts": {
|
|
2607
2607
|
template: "hooks/session-safeword-context.ts"
|
|
2608
2608
|
},
|
|
2609
|
+
".safeword/hooks/session-reply-format.ts": {
|
|
2610
|
+
template: "hooks/session-reply-format.ts"
|
|
2611
|
+
},
|
|
2609
2612
|
".safeword/hooks/session-codex-start.ts": {
|
|
2610
|
-
template: "hooks/session-codex-start.ts"
|
|
2613
|
+
template: "hooks/session-codex-start.ts",
|
|
2614
|
+
// Packaged by the CLI for native Codex dispatch, but no longer copied
|
|
2615
|
+
// into projects. Existing copies are legacy migration inputs.
|
|
2616
|
+
generator: () => void 0
|
|
2611
2617
|
},
|
|
2612
2618
|
".safeword/hooks/session-cursor-auto-upgrade.ts": {
|
|
2613
2619
|
template: "hooks/session-cursor-auto-upgrade.ts"
|
|
@@ -3166,11 +3172,9 @@ ${managedPrettierPaths(ctx).join("\n")}
|
|
|
3166
3172
|
requires: SHARED_FILING_INVARIANTS
|
|
3167
3173
|
},
|
|
3168
3174
|
"packages/cli/templates/hooks/lib/quality.ts": {
|
|
3169
|
-
// Cursor's stop hook imports QUALITY_REVIEW_MESSAGE. The
|
|
3170
|
-
//
|
|
3171
|
-
//
|
|
3172
|
-
// Removing any of them would silently regress the prompt back to legacy
|
|
3173
|
-
// free-form review.
|
|
3175
|
+
// Cursor's stop hook imports QUALITY_REVIEW_MESSAGE. The grammar, renderer,
|
|
3176
|
+
// and evaluator exports keep proactive wording and Stop validation coupled;
|
|
3177
|
+
// requiring individual labels here would create a second grammar source.
|
|
3174
3178
|
requires: [
|
|
3175
3179
|
"QUALITY_REVIEW_MESSAGE",
|
|
3176
3180
|
// prompt-questions.ts imports all three pre-response pointers; the Stop
|
|
@@ -3179,10 +3183,9 @@ ${managedPrettierPaths(ctx).join("\n")}
|
|
|
3179
3183
|
"REPLY_FORMAT_LEAD_RULE",
|
|
3180
3184
|
"REPLY_FORMAT_LEAD",
|
|
3181
3185
|
"REPLY_FORMAT_REMINDER",
|
|
3182
|
-
"
|
|
3183
|
-
"
|
|
3184
|
-
"
|
|
3185
|
-
"Need:"
|
|
3186
|
+
"DECISION_BRIEF_GRAMMAR",
|
|
3187
|
+
"renderDecisionBriefContract",
|
|
3188
|
+
"evaluateDecisionBriefCompliance"
|
|
3186
3189
|
]
|
|
3187
3190
|
},
|
|
3188
3191
|
"packages/cli/templates/doc-templates/test-definitions-feature.md": {
|
|
@@ -3227,6 +3230,7 @@ export {
|
|
|
3227
3230
|
setupPythonTooling,
|
|
3228
3231
|
hasImportLinterScaffoldTarget,
|
|
3229
3232
|
generateOwnedPathsModule,
|
|
3233
|
+
SETTINGS_HOOKS,
|
|
3230
3234
|
typescriptPackages,
|
|
3231
3235
|
filterOutSafewordHooks,
|
|
3232
3236
|
BDD_LANE_FILE_PATHS,
|
|
@@ -3234,4 +3238,4 @@ export {
|
|
|
3234
3238
|
boundaryShimCommand,
|
|
3235
3239
|
SAFEWORD_SCHEMA
|
|
3236
3240
|
};
|
|
3237
|
-
//# sourceMappingURL=chunk-
|
|
3241
|
+
//# sourceMappingURL=chunk-IWU7G6G7.js.map
|