specweave 2.1.0 → 2.2.1
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 +2 -2
- package/README.md +7 -6
- package/bin/specweave.js +28 -0
- package/dist/src/cli/commands/jev-helpers.d.ts +148 -0
- package/dist/src/cli/commands/jev-helpers.d.ts.map +1 -0
- package/dist/src/cli/commands/jev-helpers.js +582 -0
- package/dist/src/cli/commands/jev-helpers.js.map +1 -0
- package/dist/src/cli/commands/jev.d.ts +21 -0
- package/dist/src/cli/commands/jev.d.ts.map +1 -0
- package/dist/src/cli/commands/jev.js +350 -0
- package/dist/src/cli/commands/jev.js.map +1 -0
- package/dist/src/cli/helpers/init/instruction-file-writer.d.ts +4 -0
- package/dist/src/cli/helpers/init/instruction-file-writer.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/instruction-file-writer.js +11 -6
- package/dist/src/cli/helpers/init/instruction-file-writer.js.map +1 -1
- package/dist/src/core/auto/completion-evaluator.d.ts +22 -1
- package/dist/src/core/auto/completion-evaluator.d.ts.map +1 -1
- package/dist/src/core/auto/completion-evaluator.js +99 -9
- package/dist/src/core/auto/completion-evaluator.js.map +1 -1
- package/dist/src/core/config/types.d.ts +39 -1
- package/dist/src/core/config/types.d.ts.map +1 -1
- package/dist/src/core/config/types.js +1 -0
- package/dist/src/core/config/types.js.map +1 -1
- package/dist/src/core/hooks/handlers/pre-tool-use.d.ts +28 -5
- package/dist/src/core/hooks/handlers/pre-tool-use.d.ts.map +1 -1
- package/dist/src/core/hooks/handlers/pre-tool-use.js +162 -7
- package/dist/src/core/hooks/handlers/pre-tool-use.js.map +1 -1
- package/dist/src/core/jev/browse.d.ts +56 -0
- package/dist/src/core/jev/browse.d.ts.map +1 -0
- package/dist/src/core/jev/browse.js +721 -0
- package/dist/src/core/jev/browse.js.map +1 -0
- package/dist/src/core/jev/client.d.ts +134 -0
- package/dist/src/core/jev/client.d.ts.map +1 -0
- package/dist/src/core/jev/client.js +329 -0
- package/dist/src/core/jev/client.js.map +1 -0
- package/dist/src/core/jev/config.d.ts +69 -0
- package/dist/src/core/jev/config.d.ts.map +1 -0
- package/dist/src/core/jev/config.js +181 -0
- package/dist/src/core/jev/config.js.map +1 -0
- package/dist/src/core/jev/decide.d.ts +162 -0
- package/dist/src/core/jev/decide.d.ts.map +1 -0
- package/dist/src/core/jev/decide.js +333 -0
- package/dist/src/core/jev/decide.js.map +1 -0
- package/dist/src/core/jev/index.d.ts +20 -0
- package/dist/src/core/jev/index.d.ts.map +1 -0
- package/dist/src/core/jev/index.js +20 -0
- package/dist/src/core/jev/index.js.map +1 -0
- package/dist/src/core/jev/questions.d.ts +56 -0
- package/dist/src/core/jev/questions.d.ts.map +1 -0
- package/dist/src/core/jev/questions.js +159 -0
- package/dist/src/core/jev/questions.js.map +1 -0
- package/dist/src/core/jev/redact.d.ts +44 -0
- package/dist/src/core/jev/redact.d.ts.map +1 -0
- package/dist/src/core/jev/redact.js +102 -0
- package/dist/src/core/jev/redact.js.map +1 -0
- package/dist/src/core/jev/usage.d.ts +38 -0
- package/dist/src/core/jev/usage.d.ts.map +1 -0
- package/dist/src/core/jev/usage.js +70 -0
- package/dist/src/core/jev/usage.js.map +1 -0
- package/dist/src/utils/model-selection.d.ts +49 -0
- package/dist/src/utils/model-selection.d.ts.map +1 -1
- package/dist/src/utils/model-selection.js +70 -0
- package/dist/src/utils/model-selection.js.map +1 -1
- package/package.json +1 -1
- package/plugins/PLUGINS-INDEX.md +14 -4
- package/plugins/specweave/.claude-plugin/plugin.json +5 -3
- package/plugins/specweave/PLUGIN.md +16 -4
- package/plugins/specweave/hooks/run-worker.mjs +37 -2
- package/plugins/specweave/hooks/run.mjs +30 -0
- package/plugins/specweave/marketplace.json +6 -0
- package/plugins/specweave/skills/jev/SKILL.md +203 -0
- package/src/templates/AGENTS.md.template +22 -0
- package/src/templates/CLAUDE.md.template +22 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jev — the single redaction choke point.
|
|
3
|
+
*
|
|
4
|
+
* Every byte that leaves this machine for the System One endpoint goes through
|
|
5
|
+
* {@link redactSecrets} first: the client scrubs the serialized state before any
|
|
6
|
+
* request, and the PreToolUse Bash guard scrubs the command text it scores.
|
|
7
|
+
* One function, one set of patterns, one set of markers — a second copy is how a
|
|
8
|
+
* key eventually ships.
|
|
9
|
+
*
|
|
10
|
+
* Layers, in order:
|
|
11
|
+
* 1. the shared handoff scrubber (`sk-…`, `sk-or-…`, `ghp_`/`gho_`/`ghs_`,
|
|
12
|
+
* `vsk_`, `xox[abp]-`, `AKIA…`/`ASIA…`, `Bearer …`, PEM headers,
|
|
13
|
+
* `password=`, `api_key=`);
|
|
14
|
+
* 2. key shapes it does not carry (`github_pat_…`, JWT-looking `eyJ….….…`);
|
|
15
|
+
* 3. command-shaped secrets (`scheme:// USER : PASS @host`, `--otp=`/`--token …`,
|
|
16
|
+
* `SOMETHING_TOKEN=value`).
|
|
17
|
+
*
|
|
18
|
+
* Later layers carry a `(?!\[REDACTED)` guard so an already-masked span is never
|
|
19
|
+
* masked twice. Markers are normalised to `<REDACTED-…>` at the end: masking
|
|
20
|
+
* removes text, and removed text could otherwise turn a command the guard's
|
|
21
|
+
* prefilter calls 'check' into one it calls 'skip' (`echo PASSWORD="x; rm -rf ~"`
|
|
22
|
+
* → `echo PASSWORD=<…>`). `<` is in the prefilter's shell-power set, so a
|
|
23
|
+
* redacted command always still reaches Jev.
|
|
24
|
+
*
|
|
25
|
+
* HEURISTIC, never a guarantee: zero redactions does not mean the text is clean.
|
|
26
|
+
* That is why every Jev integration is opt-in and the docs state the export.
|
|
27
|
+
*
|
|
28
|
+
* @module core/jev/redact
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* Mask secret-shaped values before text leaves this machine.
|
|
32
|
+
*
|
|
33
|
+
* The markers keep the text's shape, so a command still scores the same way for
|
|
34
|
+
* scope and destructiveness and a state object still reads the same way.
|
|
35
|
+
*
|
|
36
|
+
* @param text - anything about to be sent to the System One endpoint
|
|
37
|
+
* @returns the redacted text and how many spans were masked (0 = nothing matched,
|
|
38
|
+
* which is not the same as "clean")
|
|
39
|
+
*/
|
|
40
|
+
export declare function redactSecrets(text: string): {
|
|
41
|
+
text: string;
|
|
42
|
+
redactions: number;
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=redact.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../../../src/core/jev/redact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AA4DH;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAiBhF"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jev — the single redaction choke point.
|
|
3
|
+
*
|
|
4
|
+
* Every byte that leaves this machine for the System One endpoint goes through
|
|
5
|
+
* {@link redactSecrets} first: the client scrubs the serialized state before any
|
|
6
|
+
* request, and the PreToolUse Bash guard scrubs the command text it scores.
|
|
7
|
+
* One function, one set of patterns, one set of markers — a second copy is how a
|
|
8
|
+
* key eventually ships.
|
|
9
|
+
*
|
|
10
|
+
* Layers, in order:
|
|
11
|
+
* 1. the shared handoff scrubber (`sk-…`, `sk-or-…`, `ghp_`/`gho_`/`ghs_`,
|
|
12
|
+
* `vsk_`, `xox[abp]-`, `AKIA…`/`ASIA…`, `Bearer …`, PEM headers,
|
|
13
|
+
* `password=`, `api_key=`);
|
|
14
|
+
* 2. key shapes it does not carry (`github_pat_…`, JWT-looking `eyJ….….…`);
|
|
15
|
+
* 3. command-shaped secrets (`scheme:// USER : PASS @host`, `--otp=`/`--token …`,
|
|
16
|
+
* `SOMETHING_TOKEN=value`).
|
|
17
|
+
*
|
|
18
|
+
* Later layers carry a `(?!\[REDACTED)` guard so an already-masked span is never
|
|
19
|
+
* masked twice. Markers are normalised to `<REDACTED-…>` at the end: masking
|
|
20
|
+
* removes text, and removed text could otherwise turn a command the guard's
|
|
21
|
+
* prefilter calls 'check' into one it calls 'skip' (`echo PASSWORD="x; rm -rf ~"`
|
|
22
|
+
* → `echo PASSWORD=<…>`). `<` is in the prefilter's shell-power set, so a
|
|
23
|
+
* redacted command always still reaches Jev.
|
|
24
|
+
*
|
|
25
|
+
* HEURISTIC, never a guarantee: zero redactions does not mean the text is clean.
|
|
26
|
+
* That is why every Jev integration is opt-in and the docs state the export.
|
|
27
|
+
*
|
|
28
|
+
* @module core/jev/redact
|
|
29
|
+
*/
|
|
30
|
+
import { scrubSecrets, totalRedactions } from '../session/handoff-secret-scrub.js';
|
|
31
|
+
/**
|
|
32
|
+
* Key shapes the shared scrubber does not carry.
|
|
33
|
+
*
|
|
34
|
+
* Fixed-literal prefixes, so they can never match inside a `[REDACTED-…]` marker.
|
|
35
|
+
*/
|
|
36
|
+
const KEY_SHAPE_PATTERNS = [
|
|
37
|
+
{
|
|
38
|
+
// GitHub fine-grained PAT: `github_pat_11ABCDEF0…`
|
|
39
|
+
type: 'github-pat',
|
|
40
|
+
regex: /github_pat_[A-Za-z0-9_]{20,}/g,
|
|
41
|
+
replace: '[REDACTED-github-pat]',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
// JWT-looking: `eyJhbGciOi….eyJzdWIi….<signature>` (the signature is optional).
|
|
45
|
+
type: 'jwt',
|
|
46
|
+
regex: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}(?:\.[A-Za-z0-9_-]+)?/g,
|
|
47
|
+
replace: '[REDACTED-jwt]',
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
/**
|
|
51
|
+
* Command-shaped secrets: the value is identified by the flag or variable in
|
|
52
|
+
* front of it rather than by its own shape.
|
|
53
|
+
*/
|
|
54
|
+
const COMMAND_SECRET_PATTERNS = [
|
|
55
|
+
{
|
|
56
|
+
// `psql postgres:// USER : PASS @host/db`, `git clone https:// U : TOK @github.com/...`
|
|
57
|
+
type: 'url-credentials',
|
|
58
|
+
regex: /([a-z][a-z0-9+.-]*:\/\/)[^\s/@:]+:[^\s/@]*@/gi,
|
|
59
|
+
replace: '$1[REDACTED-url-credentials]@',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
// `npm publish --otp=123456`, `--token <value>`, `--password=…`
|
|
63
|
+
type: 'secret-flag',
|
|
64
|
+
regex: /(?<![\w-])(--?(?:otp|tokens?|passwords?|passwd|secrets?|api[-_]?keys?|access[-_]?tokens?|credentials?)[=\s])(?!\[REDACTED)("[^"]*"|'[^']*'|\S+)/gi,
|
|
65
|
+
replace: '$1[REDACTED-secret-flag]',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
// `GITHUB_TOKEN=ghp_… cmd`, `MY_API_KEY="…"`, `DB_PASSWORD=…`
|
|
69
|
+
type: 'env-secret',
|
|
70
|
+
regex: /(^|[\s;&|(])([A-Za-z_][A-Za-z0-9_]*(?:TOKEN|SECRET|PASSWORD|PASSWD|KEY|CREDENTIALS?)[A-Za-z0-9_]*=)(?!\[REDACTED)("[^"]*"|'[^']*'|\S*)/gi,
|
|
71
|
+
replace: '$1$2[REDACTED-env-secret]',
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
const EXTRA_PATTERNS = [...KEY_SHAPE_PATTERNS, ...COMMAND_SECRET_PATTERNS];
|
|
75
|
+
/**
|
|
76
|
+
* Mask secret-shaped values before text leaves this machine.
|
|
77
|
+
*
|
|
78
|
+
* The markers keep the text's shape, so a command still scores the same way for
|
|
79
|
+
* scope and destructiveness and a state object still reads the same way.
|
|
80
|
+
*
|
|
81
|
+
* @param text - anything about to be sent to the System One endpoint
|
|
82
|
+
* @returns the redacted text and how many spans were masked (0 = nothing matched,
|
|
83
|
+
* which is not the same as "clean")
|
|
84
|
+
*/
|
|
85
|
+
export function redactSecrets(text) {
|
|
86
|
+
const base = scrubSecrets(text ?? '');
|
|
87
|
+
let redacted = base.scrubbed;
|
|
88
|
+
let redactions = totalRedactions(base.counts);
|
|
89
|
+
for (const { regex, replace } of EXTRA_PATTERNS) {
|
|
90
|
+
regex.lastIndex = 0;
|
|
91
|
+
const hits = redacted.match(regex);
|
|
92
|
+
if (!hits)
|
|
93
|
+
continue;
|
|
94
|
+
redactions += hits.length;
|
|
95
|
+
regex.lastIndex = 0;
|
|
96
|
+
redacted = redacted.replace(regex, replace);
|
|
97
|
+
}
|
|
98
|
+
if (redactions > 0)
|
|
99
|
+
redacted = redacted.replace(/\[REDACTED-([A-Za-z-]+)\]/g, '<REDACTED-$1>');
|
|
100
|
+
return { text: redacted, redactions };
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=redact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.js","sourceRoot":"","sources":["../../../../src/core/jev/redact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AASnF;;;;GAIG;AACH,MAAM,kBAAkB,GAAiC;IACvD;QACE,mDAAmD;QACnD,IAAI,EAAE,YAAY;QAClB,KAAK,EAAE,+BAA+B;QACtC,OAAO,EAAE,uBAAuB;KACjC;IACD;QACE,gFAAgF;QAChF,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,mEAAmE;QAC1E,OAAO,EAAE,gBAAgB;KAC1B;CACF,CAAC;AAEF;;;GAGG;AACH,MAAM,uBAAuB,GAAiC;IAC5D;QACE,wFAAwF;QACxF,IAAI,EAAE,iBAAiB;QACvB,KAAK,EAAE,+CAA+C;QACtD,OAAO,EAAE,+BAA+B;KACzC;IACD;QACE,gEAAgE;QAChE,IAAI,EAAE,aAAa;QACnB,KAAK,EACH,mJAAmJ;QACrJ,OAAO,EAAE,0BAA0B;KACpC;IACD;QACE,8DAA8D;QAC9D,IAAI,EAAE,YAAY;QAClB,KAAK,EACH,0IAA0I;QAC5I,OAAO,EAAE,2BAA2B;KACrC;CACF,CAAC;AAEF,MAAM,cAAc,GAAiC,CAAC,GAAG,kBAAkB,EAAE,GAAG,uBAAuB,CAAC,CAAC;AAEzG;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IACtC,IAAI,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;IAC7B,IAAI,UAAU,GAAG,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAE9C,KAAK,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,cAAc,EAAE,CAAC;QAChD,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC;QACpB,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACnC,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,UAAU,IAAI,IAAI,CAAC,MAAM,CAAC;QAC1B,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC;QACpB,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAC9C,CAAC;IAED,IAAI,UAAU,GAAG,CAAC;QAAE,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,4BAA4B,EAAE,eAAe,CAAC,CAAC;IAE/F,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC;AACxC,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jev usage ledger — append-only JSONL at `.specweave/state/jev-usage.jsonl`
|
|
3
|
+
*
|
|
4
|
+
* Every Jev call writes one line (tokens, cost, latency, ok). Reading is tolerant:
|
|
5
|
+
* a truncated or corrupt line is skipped, never thrown. Nothing here stores a key.
|
|
6
|
+
*
|
|
7
|
+
* @module core/jev/usage
|
|
8
|
+
*/
|
|
9
|
+
export interface JevUsageRecord {
|
|
10
|
+
at: string;
|
|
11
|
+
kind: string;
|
|
12
|
+
provider: string;
|
|
13
|
+
model: string;
|
|
14
|
+
input_tokens: number;
|
|
15
|
+
output_tokens: number;
|
|
16
|
+
cost?: number;
|
|
17
|
+
latencyMs: number;
|
|
18
|
+
/** Secret-shaped spans masked out of the request state before it was sent. */
|
|
19
|
+
redactions?: number;
|
|
20
|
+
ok: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface JevUsageSummary {
|
|
23
|
+
calls: number;
|
|
24
|
+
input_tokens: number;
|
|
25
|
+
cost: number;
|
|
26
|
+
byKind: Record<string, number>;
|
|
27
|
+
since?: string;
|
|
28
|
+
}
|
|
29
|
+
/** Absolute path of the ledger for a project root. */
|
|
30
|
+
export declare function usageLogPath(projectRoot: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* Append one record. Best-effort: never throws (the caller is mid-decision).
|
|
33
|
+
* O_APPEND keeps concurrent agents from interleaving partial lines.
|
|
34
|
+
*/
|
|
35
|
+
export declare function appendUsage(projectRoot: string, rec: JevUsageRecord): void;
|
|
36
|
+
/** Totals over the whole ledger. Missing file → zeroes. Bad lines → skipped. */
|
|
37
|
+
export declare function readUsageSummary(projectRoot: string): JevUsageSummary;
|
|
38
|
+
//# sourceMappingURL=usage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../../../../src/core/jev/usage.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,EAAE,EAAE,OAAO,CAAC;CACb;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,sDAAsD;AACtD,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,cAAc,GAAG,IAAI,CAQ1E;AAED,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,eAAe,CAuCrE"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jev usage ledger — append-only JSONL at `.specweave/state/jev-usage.jsonl`
|
|
3
|
+
*
|
|
4
|
+
* Every Jev call writes one line (tokens, cost, latency, ok). Reading is tolerant:
|
|
5
|
+
* a truncated or corrupt line is skipped, never thrown. Nothing here stores a key.
|
|
6
|
+
*
|
|
7
|
+
* @module core/jev/usage
|
|
8
|
+
*/
|
|
9
|
+
import * as fs from 'fs';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
/** Absolute path of the ledger for a project root. */
|
|
12
|
+
export function usageLogPath(projectRoot) {
|
|
13
|
+
return path.join(projectRoot, '.specweave', 'state', 'jev-usage.jsonl');
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Append one record. Best-effort: never throws (the caller is mid-decision).
|
|
17
|
+
* O_APPEND keeps concurrent agents from interleaving partial lines.
|
|
18
|
+
*/
|
|
19
|
+
export function appendUsage(projectRoot, rec) {
|
|
20
|
+
try {
|
|
21
|
+
const filePath = usageLogPath(projectRoot);
|
|
22
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
23
|
+
fs.appendFileSync(filePath, JSON.stringify(rec) + '\n', { flag: 'a' });
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// Ledger writes are advisory; a full disk must not break a Jev decision.
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Totals over the whole ledger. Missing file → zeroes. Bad lines → skipped. */
|
|
30
|
+
export function readUsageSummary(projectRoot) {
|
|
31
|
+
const summary = { calls: 0, input_tokens: 0, cost: 0, byKind: {} };
|
|
32
|
+
let raw;
|
|
33
|
+
try {
|
|
34
|
+
const filePath = usageLogPath(projectRoot);
|
|
35
|
+
if (!fs.existsSync(filePath))
|
|
36
|
+
return summary;
|
|
37
|
+
raw = fs.readFileSync(filePath, 'utf-8');
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return summary;
|
|
41
|
+
}
|
|
42
|
+
for (const line of raw.split('\n')) {
|
|
43
|
+
const trimmed = line.trim();
|
|
44
|
+
if (!trimmed)
|
|
45
|
+
continue;
|
|
46
|
+
let rec;
|
|
47
|
+
try {
|
|
48
|
+
rec = JSON.parse(trimmed);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (!rec || typeof rec !== 'object')
|
|
54
|
+
continue;
|
|
55
|
+
summary.calls += 1;
|
|
56
|
+
if (typeof rec.input_tokens === 'number' && Number.isFinite(rec.input_tokens)) {
|
|
57
|
+
summary.input_tokens += rec.input_tokens;
|
|
58
|
+
}
|
|
59
|
+
if (typeof rec.cost === 'number' && Number.isFinite(rec.cost)) {
|
|
60
|
+
summary.cost += rec.cost;
|
|
61
|
+
}
|
|
62
|
+
const kind = typeof rec.kind === 'string' && rec.kind ? rec.kind : 'unknown';
|
|
63
|
+
summary.byKind[kind] = (summary.byKind[kind] ?? 0) + 1;
|
|
64
|
+
if (typeof rec.at === 'string' && rec.at && (!summary.since || rec.at < summary.since)) {
|
|
65
|
+
summary.since = rec.at;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return summary;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=usage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage.js","sourceRoot":"","sources":["../../../../src/core/jev/usage.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* - Haiku: Simple/cheap operations, translations, mechanical work
|
|
9
9
|
* - Opus: Default for all complex work, architecture, creative problem-solving
|
|
10
10
|
*/
|
|
11
|
+
import type { JevClient } from '../core/jev/client.js';
|
|
12
|
+
import type { classifyTask } from '../core/jev/decide.js';
|
|
11
13
|
export type ModelTier = 'haiku' | 'sonnet' | 'opus';
|
|
12
14
|
export interface Task {
|
|
13
15
|
id: string;
|
|
@@ -71,4 +73,51 @@ export declare function calculateCostSavings(tasksWithModels: Map<string, ModelT
|
|
|
71
73
|
savings: number;
|
|
72
74
|
savingsPercent: number;
|
|
73
75
|
};
|
|
76
|
+
/**
|
|
77
|
+
* Where a tier recommendation came from.
|
|
78
|
+
* - `jev` — TypeSafe System One classified the task
|
|
79
|
+
* - `heuristic` — the keyword scorer in {@link detectModelForTask}
|
|
80
|
+
*/
|
|
81
|
+
export type ModelTierSource = 'jev' | 'heuristic';
|
|
82
|
+
/** {@link DetectionResult} plus the decision's provenance. */
|
|
83
|
+
export interface TierSelection extends DetectionResult {
|
|
84
|
+
source: ModelTierSource;
|
|
85
|
+
/** Latency of the Jev call, when one happened. */
|
|
86
|
+
latencyMs?: number;
|
|
87
|
+
/** Cost of the Jev call in USD, when reported. */
|
|
88
|
+
cost?: number;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Injection points for tests and callers that already hold a client/config.
|
|
92
|
+
* Nothing here is required: the defaults read the project config.
|
|
93
|
+
*/
|
|
94
|
+
export interface SelectModelTierDeps {
|
|
95
|
+
/** Project root used to load `.specweave/config.json`. */
|
|
96
|
+
projectRoot?: string;
|
|
97
|
+
/** Force the Jev path on/off (skips config loading when provided). */
|
|
98
|
+
enabled?: boolean;
|
|
99
|
+
/** Force modelRouting on/off (skips config loading when provided). */
|
|
100
|
+
modelRouting?: boolean;
|
|
101
|
+
/** Confidence floor below which the tier falls back to opus. */
|
|
102
|
+
routeThreshold?: number;
|
|
103
|
+
/** A ready client. `null` means "no client available" (heuristic path). */
|
|
104
|
+
client?: JevClient | null;
|
|
105
|
+
/** Override the classifier (tests). */
|
|
106
|
+
classify?: typeof classifyTask;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Pick a model tier for a task, preferring Jev when the project opted in.
|
|
110
|
+
*
|
|
111
|
+
* Behaviour (AC-07):
|
|
112
|
+
* - Jev off, no key, error, timeout → the keyword heuristic, `source: 'heuristic'`
|
|
113
|
+
* - Jev on → `{ model: tier, reasoning: 'jev:<complexity>', source: 'jev' }`
|
|
114
|
+
* - Jev on but confidence < `thresholds.route` → opus, `reasoning: 'jev:low-confidence'`
|
|
115
|
+
*
|
|
116
|
+
* Never throws: every failure path degrades to the heuristic.
|
|
117
|
+
*/
|
|
118
|
+
export declare function selectModelTierForTask(task: Task, options?: {
|
|
119
|
+
specDetailLevel?: number;
|
|
120
|
+
hasDetailedPlan?: boolean;
|
|
121
|
+
isArchitectural?: boolean;
|
|
122
|
+
}, deps?: SelectModelTierDeps): Promise<TierSelection>;
|
|
74
123
|
//# sourceMappingURL=model-selection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-selection.d.ts","sourceRoot":"","sources":["../../../src/utils/model-selection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEpD,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,SAAS,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AA4BD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,IAAI,EACV,OAAO,GAAE;IACP,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;CACtB,GACL,eAAe,CA6GjB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,IAAI,EAAE,EACb,OAAO,GAAE;IACP,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,OAAO,CAAC;CACtB,GACL,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAS9B;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAQxD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAU7D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,SAAS,EAChB,eAAe,GAAE,MAAa,GAC7B,MAAM,CASR;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,EACvC,oBAAoB,GAAE,MAAa,GAClC;IACD,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACxB,CAkBA"}
|
|
1
|
+
{"version":3,"file":"model-selection.d.ts","sourceRoot":"","sources":["../../../src/utils/model-selection.ts"],"names":[],"mappings":"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"}
|
|
@@ -205,4 +205,74 @@ export function calculateCostSavings(tasksWithModels, averageTokensPerTask = 200
|
|
|
205
205
|
savingsPercent
|
|
206
206
|
};
|
|
207
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Pick a model tier for a task, preferring Jev when the project opted in.
|
|
210
|
+
*
|
|
211
|
+
* Behaviour (AC-07):
|
|
212
|
+
* - Jev off, no key, error, timeout → the keyword heuristic, `source: 'heuristic'`
|
|
213
|
+
* - Jev on → `{ model: tier, reasoning: 'jev:<complexity>', source: 'jev' }`
|
|
214
|
+
* - Jev on but confidence < `thresholds.route` → opus, `reasoning: 'jev:low-confidence'`
|
|
215
|
+
*
|
|
216
|
+
* Never throws: every failure path degrades to the heuristic.
|
|
217
|
+
*/
|
|
218
|
+
export async function selectModelTierForTask(task, options = {}, deps = {}) {
|
|
219
|
+
const heuristic = () => ({
|
|
220
|
+
...detectModelForTask(task, options),
|
|
221
|
+
source: 'heuristic',
|
|
222
|
+
});
|
|
223
|
+
try {
|
|
224
|
+
const [cfgMod, clientMod, decideMod] = await Promise.all([
|
|
225
|
+
import('../core/jev/config.js'),
|
|
226
|
+
import('../core/jev/client.js'),
|
|
227
|
+
import('../core/jev/decide.js'),
|
|
228
|
+
]);
|
|
229
|
+
let enabled = deps.enabled;
|
|
230
|
+
let modelRouting = deps.modelRouting;
|
|
231
|
+
let routeThreshold = deps.routeThreshold;
|
|
232
|
+
if (enabled === undefined || modelRouting === undefined || routeThreshold === undefined) {
|
|
233
|
+
const cfg = cfgMod.loadJevConfig(deps.projectRoot);
|
|
234
|
+
if (enabled === undefined)
|
|
235
|
+
enabled = cfg.enabled;
|
|
236
|
+
if (modelRouting === undefined)
|
|
237
|
+
modelRouting = cfg.modelRouting;
|
|
238
|
+
if (routeThreshold === undefined)
|
|
239
|
+
routeThreshold = cfg.thresholds.route;
|
|
240
|
+
}
|
|
241
|
+
if (!enabled || !modelRouting)
|
|
242
|
+
return heuristic();
|
|
243
|
+
const client = deps.client !== undefined ? deps.client : clientMod.createJevClient(deps.projectRoot);
|
|
244
|
+
if (!client)
|
|
245
|
+
return heuristic();
|
|
246
|
+
const classify = deps.classify ?? decideMod.classifyTask;
|
|
247
|
+
const decision = await classify(client, {
|
|
248
|
+
id: task.id,
|
|
249
|
+
title: task.content,
|
|
250
|
+
body: task.description ?? '',
|
|
251
|
+
acs: task.acceptanceCriteria ?? [],
|
|
252
|
+
});
|
|
253
|
+
if (!decision.available)
|
|
254
|
+
return heuristic();
|
|
255
|
+
if (decision.confidence < routeThreshold) {
|
|
256
|
+
return {
|
|
257
|
+
model: 'opus',
|
|
258
|
+
confidence: decision.confidence,
|
|
259
|
+
reasoning: 'jev:low-confidence',
|
|
260
|
+
source: 'jev',
|
|
261
|
+
latencyMs: decision.latencyMs,
|
|
262
|
+
cost: decision.cost,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
model: decision.tier,
|
|
267
|
+
confidence: decision.confidence,
|
|
268
|
+
reasoning: `jev:${decision.complexity}`,
|
|
269
|
+
source: 'jev',
|
|
270
|
+
latencyMs: decision.latencyMs,
|
|
271
|
+
cost: decision.cost,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
return heuristic();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
208
278
|
//# sourceMappingURL=model-selection.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-selection.js","sourceRoot":"","sources":["../../../src/utils/model-selection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"model-selection.js","sourceRoot":"","sources":["../../../src/utils/model-selection.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specweave",
|
|
3
|
-
"version": "2.1
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"description": "Keep intent, specifications, and verified progress portable across AI coding tools. A local live work board, append-only task evidence, explicit handoffs, and optional GitHub/Jira/Azure DevOps integrations.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/plugins/PLUGINS-INDEX.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# SpecWeave plugins index
|
|
2
2
|
|
|
3
|
-
**Plugins**: 1 (`sw`) · **Skills**:
|
|
3
|
+
**Plugins**: 1 (`sw`) · **Skills**: 11 · **Commands**: 0 · **Hooks**: 2 default
|
|
4
4
|
|
|
5
5
|
The 1.x `commands/` namespace is gone: anything deterministic is a `specweave`
|
|
6
|
-
CLI subcommand, and the model-judgement procedures are the
|
|
6
|
+
CLI subcommand, and the model-judgement procedures are the 11 skills below.
|
|
7
7
|
|
|
8
8
|
| Plugin | Triggers | Description |
|
|
9
9
|
|---|---|---|
|
|
10
|
-
| **specweave** (`sw`) | increment, spec, tasks, plan, implement, close, review, team, handoff, sync, GitHub, Jira, ADO, auto, brainstorm, qa | Increment lifecycle: plan → work the ledger → verify → review → complete, with cross-tool handoff
|
|
10
|
+
| **specweave** (`sw`) | increment, spec, tasks, plan, implement, close, review, team, handoff, sync, GitHub, Jira, ADO, auto, brainstorm, qa, jev, system one | Increment lifecycle: plan → work the ledger → verify → review → complete, with cross-tool handoff, tracker sync and closed-set decisions delegated to Jev |
|
|
11
11
|
|
|
12
12
|
## Quick lookup
|
|
13
13
|
|
|
@@ -23,6 +23,16 @@ CLI subcommand, and the model-judgement procedures are the 10 skills below.
|
|
|
23
23
|
| "Run until done" | `sw:auto` |
|
|
24
24
|
| "What are our options" | `sw:brainstorm` |
|
|
25
25
|
| "Quality check" / "risk assessment" | `sw:qa` |
|
|
26
|
+
| "Jev" / "system one" / a decision whose answers can all be enumerated first | `sw:jev` |
|
|
27
|
+
|
|
28
|
+
## Hooks
|
|
29
|
+
|
|
30
|
+
Two by default, registered in `specweave/hooks/hooks.json` as exec-form
|
|
31
|
+
`node hooks/run.mjs <event>`: `SessionStart` and `Stop`. Each one fails open.
|
|
32
|
+
`PreToolUse` is not a default hook — the Jev Bash guard is turned on per project by
|
|
33
|
+
`specweave jev setup --guard-bash`, which writes `jev.guards.bash`, the marker
|
|
34
|
+
`.specweave/state/jev-guard.enabled`, and a `PreToolUse` entry (matcher `Bash`) in that
|
|
35
|
+
project's `.claude/settings.json`. `--no-guard-bash` removes all three.
|
|
26
36
|
|
|
27
37
|
## Not in the plugin
|
|
28
38
|
|
|
@@ -30,4 +40,4 @@ CLI subcommand, and the model-judgement procedures are the 10 skills below.
|
|
|
30
40
|
and install per-project with vskill. See `skills-optional/README.md`.
|
|
31
41
|
|
|
32
42
|
Deterministic operations are CLI: `specweave status | task | verify | complete | qa |
|
|
33
|
-
handoff | sync | docs | doctor | gc`.
|
|
43
|
+
handoff | sync | docs | doctor | gc | jev`.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sw",
|
|
3
|
-
"description": "Portable intent and verified progress across coding tools.
|
|
4
|
-
"version": "2.1
|
|
3
|
+
"description": "Portable intent and verified progress across coding tools. Eleven focused workflow skills, two default lifecycle hooks, and a deterministic CLI and live local dashboard.",
|
|
4
|
+
"version": "2.2.1",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "SpecWeave Team",
|
|
7
7
|
"url": "https://spec-weave.com"
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
"handoff",
|
|
21
21
|
"github",
|
|
22
22
|
"jira",
|
|
23
|
-
"azure-devops"
|
|
23
|
+
"azure-devops",
|
|
24
|
+
"jev",
|
|
25
|
+
"system-one"
|
|
24
26
|
]
|
|
25
27
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# SpecWeave plugin (`sw`)
|
|
2
2
|
|
|
3
|
-
The Claude Code surface for SpecWeave: **
|
|
3
|
+
The Claude Code surface for SpecWeave: **11 skills, 2 default hooks, 1 closer agent**.
|
|
4
4
|
Everything deterministic lives in the `specweave` CLI; a skill exists only where a
|
|
5
5
|
procedure needs model judgement. There is no `commands/` namespace in 2.0.
|
|
6
6
|
|
|
@@ -18,6 +18,7 @@ procedure needs model judgement. There is no `commands/` namespace in 2.0.
|
|
|
18
18
|
| `sw:auto` | Unattended loop driven by the Stop hook |
|
|
19
19
|
| `sw:brainstorm` | Expand the option space before committing to one |
|
|
20
20
|
| `sw:qa` | Risk-scored quality assessment (`specweave qa`) |
|
|
21
|
+
| `sw:jev` | Delegate closed-set decisions to Jev (TypeSafe System One): routing, command safety, screening |
|
|
21
22
|
|
|
22
23
|
Optional procedures (tdd-cycle, e2e, debug, diagrams, release-expert) are **not** in the
|
|
23
24
|
plugin — they ship from `skills-optional/` via
|
|
@@ -25,14 +26,25 @@ plugin — they ship from `skills-optional/` via
|
|
|
25
26
|
|
|
26
27
|
## Hooks
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
Two by default, both exec-form `node hooks/run.mjs <event>` (no shell, Windows-safe):
|
|
29
30
|
|
|
30
31
|
| Hook | Job |
|
|
31
32
|
|---|---|
|
|
32
33
|
| `SessionStart` | Inject the active increment + next task as context |
|
|
33
|
-
| `PreToolUse` (Write\|Edit) | Guard writes under `.specweave/increments/` |
|
|
34
34
|
| `Stop` | Drive the `sw:auto` loop; `{}` for every ordinary session |
|
|
35
|
-
|
|
35
|
+
|
|
36
|
+
`PreToolUse` is **not** registered by default — `hooks.json` ships `SessionStart` and
|
|
37
|
+
`Stop` only, exactly as in 2.1.0. `PreToolUse` and `PreCompact` stay callable
|
|
38
|
+
compatibility handlers.
|
|
39
|
+
|
|
40
|
+
The Jev Bash guard is opted into **one project at a time**:
|
|
41
|
+
`specweave jev setup --guard-bash` sets `jev.guards.bash` in `.specweave/config.json`,
|
|
42
|
+
writes the marker `.specweave/state/jev-guard.enabled`, and registers a project-level
|
|
43
|
+
hook in that project's `.claude/settings.json` (`hooks.PreToolUse`, matcher `Bash`,
|
|
44
|
+
command `node "<installed specweave>/plugins/specweave/hooks/run.mjs" pre-tool-use`).
|
|
45
|
+
`specweave jev setup --no-guard-bash` removes all three, and `specweave jev doctor` shows
|
|
46
|
+
the marker and the project hook. This is a Claude Code surface only; other tools call
|
|
47
|
+
`specweave jev guard "<command>"` explicitly before running anything unattended.
|
|
36
48
|
|
|
37
49
|
## Agents
|
|
38
50
|
|
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
* 4. `npm root -g` (result cached in a per-user private file under ~/.specweave)
|
|
20
20
|
*
|
|
21
21
|
* Fast path: PreToolUse for a file outside `.specweave/increments/` prints `{}`
|
|
22
|
-
* without loading the CLI at all (the guards only concern increment files)
|
|
22
|
+
* without loading the CLI at all (the guards only concern increment files), and
|
|
23
|
+
* PreToolUse for Bash prints `{}` unless the project opted into the Jev command
|
|
24
|
+
* guard with a `.specweave/state/jev-guard.enabled` marker.
|
|
23
25
|
*/
|
|
24
26
|
import { createRequire } from 'node:module';
|
|
25
27
|
import { spawnSync } from 'node:child_process';
|
|
@@ -62,6 +64,34 @@ function isPrivateOwnedFile(file) {
|
|
|
62
64
|
|
|
63
65
|
const event = process.argv[2] ?? '';
|
|
64
66
|
|
|
67
|
+
/** Opt-in marker for the Jev Bash guard, searched upward from cwd / project dir. */
|
|
68
|
+
const JEV_GUARD_MARKER = path.join('.specweave', 'state', 'jev-guard.enabled');
|
|
69
|
+
const JEV_GUARD_MAX_DEPTH = 8;
|
|
70
|
+
|
|
71
|
+
function hasJevGuardMarker(input) {
|
|
72
|
+
const starts = [typeof input.cwd === 'string' ? input.cwd : process.cwd(), process.env.CLAUDE_PROJECT_DIR];
|
|
73
|
+
for (const start of starts) {
|
|
74
|
+
if (!start) continue;
|
|
75
|
+
let current;
|
|
76
|
+
try {
|
|
77
|
+
current = path.resolve(start);
|
|
78
|
+
} catch {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
for (let depth = 0; depth < JEV_GUARD_MAX_DEPTH; depth++) {
|
|
82
|
+
try {
|
|
83
|
+
if (existsSync(path.join(current, JEV_GUARD_MARKER))) return true;
|
|
84
|
+
} catch {
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
const parent = path.dirname(current);
|
|
88
|
+
if (parent === current) break;
|
|
89
|
+
current = parent;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
|
|
65
95
|
function inactiveOutput() {
|
|
66
96
|
if (event === 'session-start') {
|
|
67
97
|
return {
|
|
@@ -123,7 +153,12 @@ function normalizeInput(raw) {
|
|
|
123
153
|
filePath = ti.file_path;
|
|
124
154
|
}
|
|
125
155
|
if (typeof input.cwd === 'string') input.cwd = input.cwd.replace(/\\/g, '/');
|
|
126
|
-
const
|
|
156
|
+
const tool = typeof input.tool_name === 'string' ? input.tool_name : '';
|
|
157
|
+
const skip =
|
|
158
|
+
event === 'pre-tool-use' &&
|
|
159
|
+
(tool === 'Bash'
|
|
160
|
+
? !hasJevGuardMarker(input)
|
|
161
|
+
: !filePath.includes('.specweave/increments/'));
|
|
127
162
|
return { raw: JSON.stringify(input), skip };
|
|
128
163
|
}
|
|
129
164
|
} catch {
|