specweave 3.0.23 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/README.md +31 -16
- package/bin/specweave.js +35 -0
- package/dist/src/cli/commands/create-increment.js +1 -1
- package/dist/src/cli/commands/create-increment.js.map +1 -1
- package/dist/src/cli/commands/glossary.d.ts +16 -0
- package/dist/src/cli/commands/glossary.d.ts.map +1 -0
- package/dist/src/cli/commands/glossary.js +55 -0
- package/dist/src/cli/commands/glossary.js.map +1 -0
- package/dist/src/cli/commands/init.d.ts +1 -1
- package/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +7 -6
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/jev-helpers.d.ts.map +1 -1
- package/dist/src/cli/commands/jev-helpers.js +5 -10
- package/dist/src/cli/commands/jev-helpers.js.map +1 -1
- package/dist/src/cli/commands/project.d.ts.map +1 -1
- package/dist/src/cli/commands/project.js +4 -2
- package/dist/src/cli/commands/project.js.map +1 -1
- package/dist/src/cli/commands/refresh-plugins.d.ts.map +1 -1
- package/dist/src/cli/commands/refresh-plugins.js +7 -1
- package/dist/src/cli/commands/refresh-plugins.js.map +1 -1
- package/dist/src/cli/commands/review.d.ts +24 -0
- package/dist/src/cli/commands/review.d.ts.map +1 -0
- package/dist/src/cli/commands/review.js +124 -0
- package/dist/src/cli/commands/review.js.map +1 -0
- package/dist/src/cli/commands/skills.d.ts +18 -0
- package/dist/src/cli/commands/skills.d.ts.map +1 -0
- package/dist/src/cli/commands/skills.js +130 -0
- package/dist/src/cli/commands/skills.js.map +1 -0
- package/dist/src/cli/commands/update.d.ts.map +1 -1
- package/dist/src/cli/commands/update.js +10 -3
- package/dist/src/cli/commands/update.js.map +1 -1
- package/dist/src/cli/helpers/init/instruction-file-writer.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/instruction-file-writer.js +4 -3
- package/dist/src/cli/helpers/init/instruction-file-writer.js.map +1 -1
- package/dist/src/cli/helpers/init/plugin-installer.js +1 -1
- package/dist/src/cli/helpers/init/plugin-installer.js.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/index.js +2 -2
- package/dist/src/core/config/types.d.ts +39 -6
- package/dist/src/core/config/types.d.ts.map +1 -1
- package/dist/src/core/config/types.js +2 -0
- package/dist/src/core/config/types.js.map +1 -1
- package/dist/src/core/glossary/check.d.ts +44 -0
- package/dist/src/core/glossary/check.d.ts.map +1 -0
- package/dist/src/core/glossary/check.js +203 -0
- package/dist/src/core/glossary/check.js.map +1 -0
- package/dist/src/core/glossary/parse.d.ts +40 -0
- package/dist/src/core/glossary/parse.d.ts.map +1 -0
- package/dist/src/core/glossary/parse.js +112 -0
- package/dist/src/core/glossary/parse.js.map +1 -0
- package/dist/src/core/hooks/handlers/stop.js +1 -1
- package/dist/src/core/hooks/handlers/stop.js.map +1 -1
- package/dist/src/core/increment/template-creator.js +1 -1
- package/dist/src/core/increment/template-creator.js.map +1 -1
- package/dist/src/core/review/brief.d.ts +46 -0
- package/dist/src/core/review/brief.d.ts.map +1 -0
- package/dist/src/core/review/brief.js +151 -0
- package/dist/src/core/review/brief.js.map +1 -0
- package/dist/src/core/review/reviewer.d.ts +62 -0
- package/dist/src/core/review/reviewer.d.ts.map +1 -0
- package/dist/src/core/review/reviewer.js +166 -0
- package/dist/src/core/review/reviewer.js.map +1 -0
- package/dist/src/core/skills/catalog.d.ts +49 -0
- package/dist/src/core/skills/catalog.d.ts.map +1 -0
- package/dist/src/core/skills/catalog.js +95 -0
- package/dist/src/core/skills/catalog.js.map +1 -0
- package/dist/src/core/skills/legacy-skill-hashes.d.ts +11 -0
- package/dist/src/core/skills/legacy-skill-hashes.d.ts.map +1 -0
- package/dist/src/core/skills/legacy-skill-hashes.js +83 -0
- package/dist/src/core/skills/legacy-skill-hashes.js.map +1 -0
- package/dist/src/core/skills/project-skills.d.ts +58 -16
- package/dist/src/core/skills/project-skills.d.ts.map +1 -1
- package/dist/src/core/skills/project-skills.js +169 -32
- package/dist/src/core/skills/project-skills.js.map +1 -1
- package/dist/src/core/tasks/closure-gate.d.ts +2 -1
- package/dist/src/core/tasks/closure-gate.d.ts.map +1 -1
- package/dist/src/core/tasks/closure-gate.js +36 -4
- package/dist/src/core/tasks/closure-gate.js.map +1 -1
- package/package.json +1 -1
- package/plugins/PLUGINS-INDEX.md +14 -12
- package/plugins/specweave/.claude-plugin/plugin.json +2 -2
- package/plugins/specweave/PLUGIN.md +24 -16
- package/plugins/specweave/marketplace.json +57 -38
- package/plugins/specweave/skills/auto/SKILL.md +8 -38
- package/plugins/specweave/skills/auto/agents/openai.yaml +5 -0
- package/plugins/specweave/skills/do/SKILL.md +46 -10
- package/plugins/specweave/skills/done/SKILL.md +6 -3
- package/plugins/specweave/skills/increment/SKILL.md +20 -11
- package/plugins/specweave/skills/review/SKILL.md +23 -4
- package/plugins/specweave/skills-catalog.json +32 -0
- package/plugins/specweave/skills-optional/glossary/SKILL.md +90 -0
- package/plugins/specweave/skills-optional/glossary/references/credits.md +33 -0
- package/plugins/specweave/skills-optional/grill-me/SKILL.md +99 -0
- package/plugins/specweave/{skills/brainstorm → skills-optional/grill-me}/references/credits.md +3 -3
- package/src/templates/AGENTS.md.template +7 -1
- package/plugins/specweave/skills/brainstorm/SKILL.md +0 -100
- package/plugins/specweave/skills/team/SKILL.md +0 -70
- /package/plugins/specweave/{skills → skills-optional}/jev/SKILL.md +0 -0
- /package/plugins/specweave/{skills → skills-optional}/project/SKILL.md +0 -0
- /package/plugins/specweave/{skills → skills-optional}/sync/SKILL.md +0 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The brief `specweave review` hands a reviewer: what the increment promised
|
|
3
|
+
* (ACs, Approach, tasks and their Files), who wrote it (model family from the
|
|
4
|
+
* ledger), the diff range and the diff itself, and the report shape to answer in.
|
|
5
|
+
*
|
|
6
|
+
* @module core/review/brief
|
|
7
|
+
*/
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import { spawnSync } from 'child_process';
|
|
11
|
+
import { readLedger, ledgerPath } from '../tasks/ledger.js';
|
|
12
|
+
import { readSpecAcs } from '../tasks/verify-runner.js';
|
|
13
|
+
import { loadTaskBoard } from '../tasks/task-board.js';
|
|
14
|
+
import { readGlossary } from '../glossary/parse.js';
|
|
15
|
+
/** Diff text above this many characters is left out; the reviewer runs git itself. */
|
|
16
|
+
export const MAX_DIFF_CHARS = 150000;
|
|
17
|
+
/** Model family of a tool name as the ledger records it (`claude@mbp` → claude). */
|
|
18
|
+
export function familyOfTool(tool) {
|
|
19
|
+
const t = tool.toLowerCase();
|
|
20
|
+
if (/^(claude|anthropic|opus|sonnet|haiku)/.test(t))
|
|
21
|
+
return 'claude';
|
|
22
|
+
if (/^(codex|openai|gpt|chatgpt)/.test(t))
|
|
23
|
+
return 'openai';
|
|
24
|
+
if (/^(grok|xai)/.test(t))
|
|
25
|
+
return 'xai';
|
|
26
|
+
if (/^(gemini|google)/.test(t))
|
|
27
|
+
return 'google';
|
|
28
|
+
return t;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Who wrote the increment, from its ledger's `done` events (claims when no task is
|
|
32
|
+
* done yet). `SPECWEAVE_AGENT` names without a tool (`api`) do not count.
|
|
33
|
+
*/
|
|
34
|
+
export function authorFamilies(incrementDir) {
|
|
35
|
+
const { events } = readLedger(ledgerPath(incrementDir));
|
|
36
|
+
const tally = (type) => {
|
|
37
|
+
const counts = {};
|
|
38
|
+
for (const e of events) {
|
|
39
|
+
if (e.e !== type || !e.by.includes('@'))
|
|
40
|
+
continue;
|
|
41
|
+
const family = familyOfTool(e.by.split('@')[0]);
|
|
42
|
+
counts[family] = (counts[family] ?? 0) + 1;
|
|
43
|
+
}
|
|
44
|
+
return counts;
|
|
45
|
+
};
|
|
46
|
+
let counts = tally('done');
|
|
47
|
+
if (!Object.keys(counts).length)
|
|
48
|
+
counts = tally('claim');
|
|
49
|
+
const primary = Object.entries(counts).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))[0]?.[0];
|
|
50
|
+
return { primary, counts };
|
|
51
|
+
}
|
|
52
|
+
export function git(cwd, args) {
|
|
53
|
+
const res = spawnSync('git', args, { cwd, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
|
|
54
|
+
return res.status === 0 ? res.stdout : null;
|
|
55
|
+
}
|
|
56
|
+
/** `--base`, else the merge-base with the remote's default branch, develop, main or master. */
|
|
57
|
+
export function resolveBase(repoRoot, explicit) {
|
|
58
|
+
const candidates = explicit
|
|
59
|
+
? [explicit]
|
|
60
|
+
: ['origin/HEAD', 'origin/develop', 'origin/main', 'origin/master', 'develop', 'main', 'master'];
|
|
61
|
+
for (const ref of candidates) {
|
|
62
|
+
const sha = git(repoRoot, ['merge-base', 'HEAD', ref])?.trim();
|
|
63
|
+
if (sha)
|
|
64
|
+
return sha;
|
|
65
|
+
if (explicit)
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
function section(spec, heading) {
|
|
71
|
+
const lines = spec.replace(/\r\n/g, '\n').split('\n');
|
|
72
|
+
const start = lines.findIndex((l) => l.trim().toLowerCase() === `## ${heading}`.toLowerCase());
|
|
73
|
+
if (start < 0)
|
|
74
|
+
return '';
|
|
75
|
+
const end = lines.findIndex((l, i) => i > start && /^##\s/.test(l));
|
|
76
|
+
return lines.slice(start + 1, end < 0 ? undefined : end).join('\n').trim();
|
|
77
|
+
}
|
|
78
|
+
export function buildBrief(opts) {
|
|
79
|
+
const { projectRoot, repoRoot, incrementId, incrementDir, base } = opts;
|
|
80
|
+
const specFile = path.join(incrementDir, 'spec.md');
|
|
81
|
+
const spec = fs.existsSync(specFile) ? fs.readFileSync(specFile, 'utf8') : '';
|
|
82
|
+
const title = spec.match(/^#\s+(.+)$/m)?.[1].trim() ?? incrementId;
|
|
83
|
+
const author = authorFamilies(incrementDir);
|
|
84
|
+
const acs = readSpecAcs(incrementDir);
|
|
85
|
+
let tasks = [];
|
|
86
|
+
try {
|
|
87
|
+
tasks = loadTaskBoard(incrementDir).tasks.map((t) => ({
|
|
88
|
+
id: t.id, title: t.title, files: t.filesAffected ?? [], test: t.test, status: t.state.status,
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// unreadable tasks: the brief still carries the ACs and the diff
|
|
93
|
+
}
|
|
94
|
+
const paths = [...new Set(tasks.flatMap((t) => t.files))]
|
|
95
|
+
.map((f) => path.relative(repoRoot, path.resolve(projectRoot, f.replace(/\/$/, ''))))
|
|
96
|
+
.filter((f) => f && !f.startsWith('..') && !path.isAbsolute(f))
|
|
97
|
+
.map((f) => f.split(path.sep).join('/'));
|
|
98
|
+
const head = git(repoRoot, ['rev-parse', 'HEAD'])?.trim() ?? 'HEAD';
|
|
99
|
+
const range = `${base}...HEAD`;
|
|
100
|
+
const scope = paths.length ? ['--', ...paths] : [];
|
|
101
|
+
const stat = git(repoRoot, ['diff', '--stat', range, ...scope])?.trim() ?? '';
|
|
102
|
+
const diff = git(repoRoot, ['diff', range, ...scope]) ?? '';
|
|
103
|
+
const diffIncluded = diff.length > 0 && diff.length <= MAX_DIFF_CHARS;
|
|
104
|
+
const diffCmd = `git diff ${base.slice(0, 12)}...HEAD${paths.length ? ` -- ${paths.join(' ')}` : ''}`;
|
|
105
|
+
const glossary = readGlossary(projectRoot);
|
|
106
|
+
const authorLine = author.primary
|
|
107
|
+
? `${author.primary} (${Object.entries(author.counts).map(([f, n]) => `${f} ${n}`).join(', ')} finished tasks)`
|
|
108
|
+
: 'unknown (no finished tasks in the ledger)';
|
|
109
|
+
const parts = [
|
|
110
|
+
`# Adversarial review: ${incrementId} ${title}`,
|
|
111
|
+
'',
|
|
112
|
+
'You are reviewing code another model wrote. Do not edit files. Hunt for real failures:',
|
|
113
|
+
'input or state that leads to wrong output, a crash, a hang, data loss, a leak, or an',
|
|
114
|
+
'acceptance criterion claimed but not met. Every finding cites `path:line` and the',
|
|
115
|
+
'concrete failure. Re-open the lines before you report a finding; drop what does not',
|
|
116
|
+
'survive. "Consider refactoring" is not a finding.',
|
|
117
|
+
'',
|
|
118
|
+
`Repository: ${repoRoot}`,
|
|
119
|
+
`Written by: ${authorLine}`,
|
|
120
|
+
`Diff: ${diffCmd} (base ${base}, head ${head})`,
|
|
121
|
+
'',
|
|
122
|
+
'Order: correctness (logic, error paths, races, off-by-one), security (injection, path',
|
|
123
|
+
'traversal, secrets, unsafe exec), spec compliance (ACs below; tests that assert nothing',
|
|
124
|
+
'or mock the subject), portability (Windows paths, shells, encodings).',
|
|
125
|
+
'',
|
|
126
|
+
];
|
|
127
|
+
if (acs.length) {
|
|
128
|
+
parts.push('## Acceptance criteria', '', ...acs.map((a) => `- ${a.id}: ${a.text}`), '');
|
|
129
|
+
}
|
|
130
|
+
const approach = section(spec, 'Approach');
|
|
131
|
+
if (approach)
|
|
132
|
+
parts.push('## Approach', '', approach, '');
|
|
133
|
+
if (tasks.length) {
|
|
134
|
+
parts.push('## Tasks', '');
|
|
135
|
+
for (const t of tasks) {
|
|
136
|
+
parts.push(`- ${t.id} ${t.title} [${t.status}]${t.files.length ? ` · Files: ${t.files.join(', ')}` : ''}${t.test ? ` · Test: ${t.test}` : ''}`);
|
|
137
|
+
}
|
|
138
|
+
parts.push('');
|
|
139
|
+
}
|
|
140
|
+
if (glossary.length) {
|
|
141
|
+
parts.push('## Glossary', '', 'New names must use these terms; an avoided word in a new identifier is a low finding.', '', ...glossary.map((g) => `- ${g.term}${g.avoid.length ? ` (avoid: ${g.avoid.join(', ')})` : ''}`), '');
|
|
142
|
+
}
|
|
143
|
+
parts.push('## Diff stat', '', '```', stat || '(empty diff)', '```', '');
|
|
144
|
+
if (diffIncluded)
|
|
145
|
+
parts.push('## Diff', '', '```diff', diff.trimEnd(), '```', '');
|
|
146
|
+
else if (diff.length)
|
|
147
|
+
parts.push(`The diff is ${diff.length} characters; run \`${diffCmd}\` and read it in parts.`, '');
|
|
148
|
+
parts.push('## Answer in exactly this shape', '', '```markdown', `# Review: ${incrementId} ${title}`, 'Verdict: fix first · risk: high · 6 files · 2 findings (1 critical, 1 high)', '', '## [critical] src/core/tasks/ledger.ts:142 lost claim under equal timestamps', 'Two agents claim T-01 in the same second; the fold sorts only by `at`, so both read', 'themselves as owner. Fix: break ties by `by`, then file order.', '', 'Nits: at most 5, on one line.', '```', '', 'Severity is critical, high, medium or low. The verdict is `fix first` while any critical', 'or high finding survives, otherwise `ship`. The second line must start with `Verdict:`.');
|
|
149
|
+
return { incrementId, title, author, base, head, paths, stat, diffIncluded, text: parts.join('\n') + '\n' };
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=brief.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brief.js","sourceRoot":"","sources":["../../../../src/core/review/brief.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reviewers for `specweave review`: a CLI command per reviewer that reads the
|
|
3
|
+
* brief on stdin and answers on stdout, or into `{out}` when the command names
|
|
4
|
+
* one. SpecWeave holds no model keys; each CLI uses its own login.
|
|
5
|
+
*
|
|
6
|
+
* Defaults: Codex with GPT-6.1-Sol for code another family wrote, Claude Opus for
|
|
7
|
+
* code OpenAI models wrote. `review.reviewers` in config overrides or adds.
|
|
8
|
+
*
|
|
9
|
+
* @module core/review/reviewer
|
|
10
|
+
*/
|
|
11
|
+
import type { ReviewConfiguration, ReviewerConfiguration } from '../config/types.js';
|
|
12
|
+
export interface Reviewer extends ReviewerConfiguration {
|
|
13
|
+
name: string;
|
|
14
|
+
family: string;
|
|
15
|
+
}
|
|
16
|
+
export declare const BUILTIN_REVIEWERS: Record<string, ReviewerConfiguration>;
|
|
17
|
+
export declare const DEFAULT_TIMEOUT_MINUTES = 30;
|
|
18
|
+
export declare function reviewersFrom(config: ReviewConfiguration | undefined): Reviewer[];
|
|
19
|
+
/** First word of a command, unquoted. */
|
|
20
|
+
export declare function commandBinary(command: string): string;
|
|
21
|
+
export declare function onPath(binary: string, env?: NodeJS.ProcessEnv): boolean;
|
|
22
|
+
export interface ReviewerChoice {
|
|
23
|
+
reviewer?: Reviewer;
|
|
24
|
+
/** Why this one, or why none. */
|
|
25
|
+
why: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* `--reviewer`, else `review.reviewer` unless it is `auto`, else the first installed
|
|
29
|
+
* reviewer of another family than the author (Codex first). An unknown author gets
|
|
30
|
+
* Codex, since most code SpecWeave sees is written by Claude.
|
|
31
|
+
*/
|
|
32
|
+
export declare function pickReviewer(reviewers: Reviewer[], authorFamily: string | undefined, opts?: {
|
|
33
|
+
explicit?: string;
|
|
34
|
+
configured?: string;
|
|
35
|
+
available?: (r: Reviewer) => boolean;
|
|
36
|
+
}): ReviewerChoice;
|
|
37
|
+
/** Env var holding the answer file; `{out}` becomes a quoted reference to it, never the path itself. */
|
|
38
|
+
export declare const OUT_ENV = "SPECWEAVE_REVIEW_OUT";
|
|
39
|
+
/** `{out}`, `"{out}"` or `'{out}'` → `"$SPECWEAVE_REVIEW_OUT"` (`"%SPECWEAVE_REVIEW_OUT%"` on Windows). */
|
|
40
|
+
export declare function expandOut(command: string, platform?: NodeJS.Platform): string;
|
|
41
|
+
export interface RunResult {
|
|
42
|
+
code: number;
|
|
43
|
+
output: string;
|
|
44
|
+
timedOut: boolean;
|
|
45
|
+
stderr: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Run a reviewer with the brief on stdin; its answer is `{out}` when used, else
|
|
49
|
+
* stdout. On timeout the whole process tree is stopped, and the result returns
|
|
50
|
+
* within two grace periods even if a descendant keeps the pipes open.
|
|
51
|
+
*/
|
|
52
|
+
export declare function runReviewer(command: string, brief: string, opts: {
|
|
53
|
+
cwd: string;
|
|
54
|
+
outFile: string;
|
|
55
|
+
timeoutMs: number;
|
|
56
|
+
env?: NodeJS.ProcessEnv;
|
|
57
|
+
graceMs?: number;
|
|
58
|
+
}): Promise<RunResult>;
|
|
59
|
+
export type Verdict = 'ship' | 'fix first';
|
|
60
|
+
/** `Verdict: fix first` / `**Verdict:** ship` anywhere in the answer; the first one wins. */
|
|
61
|
+
export declare function parseVerdict(text: string): Verdict | undefined;
|
|
62
|
+
//# sourceMappingURL=reviewer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reviewer.d.ts","sourceRoot":"","sources":["../../../../src/core/review/reviewer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,KAAK,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAGrF,MAAM,WAAW,QAAS,SAAQ,qBAAqB;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CASnE,CAAC;AAEF,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAE1C,wBAAgB,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,SAAS,GAAG,QAAQ,EAAE,CAMjF;AAED,yCAAyC;AACzC,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGrD;AAED,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAKpF;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,iCAAiC;IACjC,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,SAAS,EAAE,QAAQ,EAAE,EACrB,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,QAAQ,KAAK,OAAO,CAAA;CAAO,GAC1F,cAAc,CAgBhB;AAED,wGAAwG;AACxG,eAAO,MAAM,OAAO,yBAAyB,CAAC;AAE9C,2GAA2G;AAC3G,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAAG,MAAM,CAG/F;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAKD;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,IAAI,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GACnG,OAAO,CAAC,SAAS,CAAC,CAoEpB;AAED,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,WAAW,CAAC;AAE3C,6FAA6F;AAC7F,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAI9D"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reviewers for `specweave review`: a CLI command per reviewer that reads the
|
|
3
|
+
* brief on stdin and answers on stdout, or into `{out}` when the command names
|
|
4
|
+
* one. SpecWeave holds no model keys; each CLI uses its own login.
|
|
5
|
+
*
|
|
6
|
+
* Defaults: Codex with GPT-6.1-Sol for code another family wrote, Claude Opus for
|
|
7
|
+
* code OpenAI models wrote. `review.reviewers` in config overrides or adds.
|
|
8
|
+
*
|
|
9
|
+
* @module core/review/reviewer
|
|
10
|
+
*/
|
|
11
|
+
import * as fs from 'fs';
|
|
12
|
+
import * as path from 'path';
|
|
13
|
+
import { spawn, spawnSync } from 'child_process';
|
|
14
|
+
import { familyOfTool } from './brief.js';
|
|
15
|
+
export const BUILTIN_REVIEWERS = {
|
|
16
|
+
codex: {
|
|
17
|
+
command: 'codex exec -m gpt-6.1-sol -c model_reasoning_effort="high" -s read-only --skip-git-repo-check -o {out} -',
|
|
18
|
+
family: 'openai',
|
|
19
|
+
},
|
|
20
|
+
claude: {
|
|
21
|
+
command: 'claude -p --model opus --permission-mode plan',
|
|
22
|
+
family: 'claude',
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
export const DEFAULT_TIMEOUT_MINUTES = 30;
|
|
26
|
+
export function reviewersFrom(config) {
|
|
27
|
+
const merged = { ...BUILTIN_REVIEWERS };
|
|
28
|
+
for (const [name, r] of Object.entries(config?.reviewers ?? {})) {
|
|
29
|
+
if (r && typeof r.command === 'string' && r.command.trim())
|
|
30
|
+
merged[name] = { ...BUILTIN_REVIEWERS[name], ...r };
|
|
31
|
+
}
|
|
32
|
+
return Object.entries(merged).map(([name, r]) => ({ name, command: r.command, family: familyOfTool(r.family ?? name) }));
|
|
33
|
+
}
|
|
34
|
+
/** First word of a command, unquoted. */
|
|
35
|
+
export function commandBinary(command) {
|
|
36
|
+
const m = command.trim().match(/^"([^"]+)"|^'([^']+)'|^(\S+)/);
|
|
37
|
+
return m ? (m[1] ?? m[2] ?? m[3]) : '';
|
|
38
|
+
}
|
|
39
|
+
export function onPath(binary, env = process.env) {
|
|
40
|
+
if (!binary)
|
|
41
|
+
return false;
|
|
42
|
+
if (binary.includes('/') || binary.includes('\\'))
|
|
43
|
+
return fs.existsSync(binary);
|
|
44
|
+
const probe = process.platform === 'win32' ? spawnSync('where', [binary], { env }) : spawnSync('sh', ['-c', 'command -v "$1"', 'sh', binary], { env });
|
|
45
|
+
return probe.status === 0;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* `--reviewer`, else `review.reviewer` unless it is `auto`, else the first installed
|
|
49
|
+
* reviewer of another family than the author (Codex first). An unknown author gets
|
|
50
|
+
* Codex, since most code SpecWeave sees is written by Claude.
|
|
51
|
+
*/
|
|
52
|
+
export function pickReviewer(reviewers, authorFamily, opts = {}) {
|
|
53
|
+
const available = opts.available ?? ((r) => onPath(commandBinary(r.command)));
|
|
54
|
+
const named = opts.explicit || (opts.configured && opts.configured !== 'auto' ? opts.configured : undefined);
|
|
55
|
+
if (named) {
|
|
56
|
+
const r = reviewers.find((x) => x.name === named);
|
|
57
|
+
if (!r)
|
|
58
|
+
return { why: `no reviewer "${named}"; known: ${reviewers.map((x) => x.name).join(', ')}` };
|
|
59
|
+
return { reviewer: r, why: opts.explicit ? '--reviewer' : 'review.reviewer' };
|
|
60
|
+
}
|
|
61
|
+
const order = [...reviewers].sort((a, b) => Number(b.name === 'codex') - Number(a.name === 'codex'));
|
|
62
|
+
const other = order.filter((r) => r.family !== authorFamily);
|
|
63
|
+
const pick = other.find(available);
|
|
64
|
+
if (pick) {
|
|
65
|
+
return { reviewer: pick, why: authorFamily ? `another family than the author (${authorFamily})` : 'author unknown' };
|
|
66
|
+
}
|
|
67
|
+
const missing = other.map((r) => commandBinary(r.command)).join(', ') || 'none configured';
|
|
68
|
+
return { why: `no reviewer from another family than ${authorFamily ?? 'the author'} is installed (${missing})` };
|
|
69
|
+
}
|
|
70
|
+
/** Env var holding the answer file; `{out}` becomes a quoted reference to it, never the path itself. */
|
|
71
|
+
export const OUT_ENV = 'SPECWEAVE_REVIEW_OUT';
|
|
72
|
+
/** `{out}`, `"{out}"` or `'{out}'` → `"$SPECWEAVE_REVIEW_OUT"` (`"%SPECWEAVE_REVIEW_OUT%"` on Windows). */
|
|
73
|
+
export function expandOut(command, platform = process.platform) {
|
|
74
|
+
const ref = platform === 'win32' ? `"%${OUT_ENV}%"` : `"$${OUT_ENV}"`;
|
|
75
|
+
return command.replace(/(["']?)\{out\}\1/g, ref);
|
|
76
|
+
}
|
|
77
|
+
/** Grace between SIGTERM and SIGKILL, and again before giving up on the pipes. */
|
|
78
|
+
const KILL_GRACE_MS = 5000;
|
|
79
|
+
/**
|
|
80
|
+
* Run a reviewer with the brief on stdin; its answer is `{out}` when used, else
|
|
81
|
+
* stdout. On timeout the whole process tree is stopped, and the result returns
|
|
82
|
+
* within two grace periods even if a descendant keeps the pipes open.
|
|
83
|
+
*/
|
|
84
|
+
export function runReviewer(command, brief, opts) {
|
|
85
|
+
const usesOut = command.includes('{out}');
|
|
86
|
+
const cmd = expandOut(command);
|
|
87
|
+
const grace = opts.graceMs ?? KILL_GRACE_MS;
|
|
88
|
+
if (usesOut) {
|
|
89
|
+
fs.mkdirSync(path.dirname(opts.outFile), { recursive: true });
|
|
90
|
+
fs.rmSync(opts.outFile, { force: true });
|
|
91
|
+
}
|
|
92
|
+
return new Promise((resolve) => {
|
|
93
|
+
const posix = process.platform !== 'win32';
|
|
94
|
+
const child = spawn(cmd, {
|
|
95
|
+
cwd: opts.cwd,
|
|
96
|
+
shell: true,
|
|
97
|
+
// Own process group on POSIX so a timeout or Ctrl-C stops the reviewer's children too.
|
|
98
|
+
detached: posix,
|
|
99
|
+
env: { ...(opts.env ?? process.env), [OUT_ENV]: opts.outFile },
|
|
100
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
101
|
+
windowsHide: true,
|
|
102
|
+
});
|
|
103
|
+
let stdout = '';
|
|
104
|
+
let stderr = '';
|
|
105
|
+
let timedOut = false;
|
|
106
|
+
let done = false;
|
|
107
|
+
const killTree = (signal) => {
|
|
108
|
+
if (!child.pid)
|
|
109
|
+
return;
|
|
110
|
+
try {
|
|
111
|
+
if (posix)
|
|
112
|
+
process.kill(-child.pid, signal);
|
|
113
|
+
else
|
|
114
|
+
spawnSync('taskkill', ['/PID', String(child.pid), '/T', '/F'], { stdio: 'ignore', windowsHide: true, timeout: 2000 });
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// already gone
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
// Ctrl-C or a kill of `specweave review`: there is no time left to escalate, so
|
|
121
|
+
// the reviewer's group is killed outright before the signal is passed on.
|
|
122
|
+
const onSignal = (signal) => {
|
|
123
|
+
killTree('SIGKILL');
|
|
124
|
+
process.kill(process.pid, signal);
|
|
125
|
+
};
|
|
126
|
+
process.once('SIGINT', onSignal);
|
|
127
|
+
process.once('SIGTERM', onSignal);
|
|
128
|
+
child.stdout?.on('data', (d) => { stdout += d.toString('utf8'); });
|
|
129
|
+
child.stderr?.on('data', (d) => { stderr = (stderr + d.toString('utf8')).slice(-8000); });
|
|
130
|
+
child.stdin?.on('error', () => { });
|
|
131
|
+
child.stdin?.end(brief);
|
|
132
|
+
const timers = [];
|
|
133
|
+
const finish = (code) => {
|
|
134
|
+
if (done)
|
|
135
|
+
return;
|
|
136
|
+
done = true;
|
|
137
|
+
timers.forEach(clearTimeout);
|
|
138
|
+
// Whatever the reviewer left behind (a descendant that ignored SIGTERM, a
|
|
139
|
+
// background job) goes with it: the group is ours alone.
|
|
140
|
+
if (posix || timedOut)
|
|
141
|
+
killTree('SIGKILL');
|
|
142
|
+
process.removeListener('SIGINT', onSignal);
|
|
143
|
+
process.removeListener('SIGTERM', onSignal);
|
|
144
|
+
const fromFile = usesOut && fs.existsSync(opts.outFile) ? fs.readFileSync(opts.outFile, 'utf8') : '';
|
|
145
|
+
resolve({ code, output: (fromFile || stdout).trim(), timedOut, stderr: stderr.trim() });
|
|
146
|
+
};
|
|
147
|
+
timers.push(setTimeout(() => {
|
|
148
|
+
timedOut = true;
|
|
149
|
+
killTree('SIGTERM');
|
|
150
|
+
timers.push(setTimeout(() => {
|
|
151
|
+
killTree('SIGKILL');
|
|
152
|
+
timers.push(setTimeout(() => finish(124), grace));
|
|
153
|
+
}, grace));
|
|
154
|
+
}, opts.timeoutMs));
|
|
155
|
+
child.on('close', (code) => finish(code ?? 1));
|
|
156
|
+
child.on('error', (e) => { stderr += String(e); finish(127); });
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
/** `Verdict: fix first` / `**Verdict:** ship` anywhere in the answer; the first one wins. */
|
|
160
|
+
export function parseVerdict(text) {
|
|
161
|
+
const m = text.match(/^[\s>*_#-]*verdict[*_]*\s*:[*_\s]*(ship|fix[ -]first)\b/im);
|
|
162
|
+
if (!m)
|
|
163
|
+
return undefined;
|
|
164
|
+
return m[1].toLowerCase() === 'ship' ? 'ship' : 'fix first';
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=reviewer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reviewer.js","sourceRoot":"","sources":["../../../../src/core/review/reviewer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill catalog: which SpecWeave skills a project gets.
|
|
3
|
+
*
|
|
4
|
+
* `skills/catalog.json` (copied to `plugins/specweave/skills-catalog.json` by
|
|
5
|
+
* scripts/build/generate-skills.mjs) lists the core skills every project gets,
|
|
6
|
+
* the optional ones, and the retired ones `update` removes. An optional skill is
|
|
7
|
+
* on when `.specweave/config.json` lists it under `skills.enabled`, or when its
|
|
8
|
+
* detect rule matches and it is not under `skills.disabled`.
|
|
9
|
+
*
|
|
10
|
+
* @module core/skills/catalog
|
|
11
|
+
*/
|
|
12
|
+
export type DetectRule = 'deep-interview' | 'glossary-file' | 'tracker' | 'hub' | 'jev';
|
|
13
|
+
export interface OptionalSkill {
|
|
14
|
+
name: string;
|
|
15
|
+
summary: string;
|
|
16
|
+
detect?: DetectRule;
|
|
17
|
+
}
|
|
18
|
+
export interface SkillCatalog {
|
|
19
|
+
core: string[];
|
|
20
|
+
optional: OptionalSkill[];
|
|
21
|
+
retired: string[];
|
|
22
|
+
}
|
|
23
|
+
export interface SkillsConfig {
|
|
24
|
+
/** Optional skills to install even when nothing detects them. */
|
|
25
|
+
enabled?: string[];
|
|
26
|
+
/** Optional skills to leave out even when detected. */
|
|
27
|
+
disabled?: string[];
|
|
28
|
+
}
|
|
29
|
+
export type SkillState = {
|
|
30
|
+
name: string;
|
|
31
|
+
tier: 'core';
|
|
32
|
+
on: true;
|
|
33
|
+
reason: 'core';
|
|
34
|
+
} | {
|
|
35
|
+
name: string;
|
|
36
|
+
tier: 'optional';
|
|
37
|
+
on: boolean;
|
|
38
|
+
reason: 'enabled' | 'disabled' | 'detected' | 'off';
|
|
39
|
+
detail?: string;
|
|
40
|
+
summary: string;
|
|
41
|
+
};
|
|
42
|
+
export declare function readSkillCatalog(pluginRoot: string): SkillCatalog;
|
|
43
|
+
/** Why a detect rule matches in this project, or undefined when it does not. */
|
|
44
|
+
export declare function detectSkill(rule: DetectRule | undefined, projectRoot: string, config: Record<string, any> | undefined): string | undefined;
|
|
45
|
+
/** Every catalog skill with its state in this project, core first. */
|
|
46
|
+
export declare function resolveSkills(projectRoot: string, catalog: SkillCatalog, config?: Record<string, any> | undefined): SkillState[];
|
|
47
|
+
/** Names in a config list that are not optional skills (typos, core skills). */
|
|
48
|
+
export declare function unknownSkillNames(list: unknown, catalog: SkillCatalog): string[];
|
|
49
|
+
//# sourceMappingURL=catalog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../../../src/core/skills/catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,MAAM,MAAM,UAAU,GAAG,gBAAgB,GAAG,eAAe,GAAG,SAAS,GAAG,KAAK,GAAG,KAAK,CAAC;AAExF,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,iEAAiE;IACjE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACxD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,CAAC;IAAC,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,KAAK,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3I,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY,CAOjE;AA4BD,gFAAgF;AAChF,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAgB1I;AAED,sEAAsE;AACtE,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,kCAA0B,GAAG,UAAU,EAAE,CAexH;AAED,gFAAgF;AAChF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,GAAG,MAAM,EAAE,CAGhF"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill catalog: which SpecWeave skills a project gets.
|
|
3
|
+
*
|
|
4
|
+
* `skills/catalog.json` (copied to `plugins/specweave/skills-catalog.json` by
|
|
5
|
+
* scripts/build/generate-skills.mjs) lists the core skills every project gets,
|
|
6
|
+
* the optional ones, and the retired ones `update` removes. An optional skill is
|
|
7
|
+
* on when `.specweave/config.json` lists it under `skills.enabled`, or when its
|
|
8
|
+
* detect rule matches and it is not under `skills.disabled`.
|
|
9
|
+
*
|
|
10
|
+
* @module core/skills/catalog
|
|
11
|
+
*/
|
|
12
|
+
import * as fs from 'fs';
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
export function readSkillCatalog(pluginRoot) {
|
|
15
|
+
const file = path.join(pluginRoot, 'skills-catalog.json');
|
|
16
|
+
const raw = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
17
|
+
if (!Array.isArray(raw.core) || !Array.isArray(raw.optional)) {
|
|
18
|
+
throw new Error(`${file}: needs "core" and "optional" arrays`);
|
|
19
|
+
}
|
|
20
|
+
return { core: raw.core, optional: raw.optional, retired: raw.retired ?? [] };
|
|
21
|
+
}
|
|
22
|
+
function readConfig(projectRoot) {
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(fs.readFileSync(path.join(projectRoot, '.specweave', 'config.json'), 'utf8')) ?? undefined;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Short names from a config list; tolerates `sw-` prefixes and non-arrays. */
|
|
31
|
+
function names(list) {
|
|
32
|
+
if (!Array.isArray(list))
|
|
33
|
+
return new Set();
|
|
34
|
+
return new Set(list.filter((n) => typeof n === 'string').map((n) => n.replace(/^sw-/, '')));
|
|
35
|
+
}
|
|
36
|
+
function trackerConfigured(config) {
|
|
37
|
+
const sync = config?.sync;
|
|
38
|
+
for (const provider of ['github', 'jira', 'ado']) {
|
|
39
|
+
if (sync?.[provider]?.enabled === true)
|
|
40
|
+
return `sync.${provider}.enabled`;
|
|
41
|
+
}
|
|
42
|
+
if (sync?.profiles && typeof sync.profiles === 'object' && Object.keys(sync.profiles).length > 0)
|
|
43
|
+
return 'sync.profiles';
|
|
44
|
+
if (sync?.enabled === true)
|
|
45
|
+
return 'sync.enabled';
|
|
46
|
+
const tracker = config?.issueTracker?.provider;
|
|
47
|
+
if (typeof tracker === 'string' && tracker !== '' && tracker !== 'none')
|
|
48
|
+
return `issueTracker.provider: ${tracker}`;
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
/** Why a detect rule matches in this project, or undefined when it does not. */
|
|
52
|
+
export function detectSkill(rule, projectRoot, config) {
|
|
53
|
+
switch (rule) {
|
|
54
|
+
case 'deep-interview':
|
|
55
|
+
return config?.planning?.deepInterview === 'deep' ? 'planning.deepInterview: deep' : undefined;
|
|
56
|
+
case 'glossary-file':
|
|
57
|
+
for (const f of ['GLOSSARY.md', 'GLOSSARY-MAP.md'])
|
|
58
|
+
if (fs.existsSync(path.join(projectRoot, f)))
|
|
59
|
+
return f;
|
|
60
|
+
return undefined;
|
|
61
|
+
case 'tracker':
|
|
62
|
+
return trackerConfigured(config);
|
|
63
|
+
case 'hub':
|
|
64
|
+
return fs.existsSync(path.join(projectRoot, '.specweave', 'project', 'hub.json')) ? '.specweave/project/hub.json' : undefined;
|
|
65
|
+
case 'jev':
|
|
66
|
+
return config?.jev?.enabled === true ? 'jev.enabled' : undefined;
|
|
67
|
+
default:
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Every catalog skill with its state in this project, core first. */
|
|
72
|
+
export function resolveSkills(projectRoot, catalog, config = readConfig(projectRoot)) {
|
|
73
|
+
const skills = (config?.skills ?? {});
|
|
74
|
+
const enabled = names(skills.enabled);
|
|
75
|
+
const disabled = names(skills.disabled);
|
|
76
|
+
const out = catalog.core.map((name) => ({ name, tier: 'core', on: true, reason: 'core' }));
|
|
77
|
+
for (const s of catalog.optional) {
|
|
78
|
+
const base = { name: s.name, tier: 'optional', summary: s.summary };
|
|
79
|
+
if (enabled.has(s.name))
|
|
80
|
+
out.push({ ...base, on: true, reason: 'enabled' });
|
|
81
|
+
else if (disabled.has(s.name))
|
|
82
|
+
out.push({ ...base, on: false, reason: 'disabled' });
|
|
83
|
+
else {
|
|
84
|
+
const why = detectSkill(s.detect, projectRoot, config);
|
|
85
|
+
out.push(why ? { ...base, on: true, reason: 'detected', detail: why } : { ...base, on: false, reason: 'off' });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
/** Names in a config list that are not optional skills (typos, core skills). */
|
|
91
|
+
export function unknownSkillNames(list, catalog) {
|
|
92
|
+
const optional = new Set(catalog.optional.map((s) => s.name));
|
|
93
|
+
return [...names(list)].filter((n) => !optional.has(n));
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=catalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../../../src/core/skills/catalog.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SHA-256 of every file SpecWeave 3.0.0 to 3.0.23 installed into `.claude/skills/sw-<name>/` and
|
|
3
|
+
* `.agents/skills/sw-<name>/` (the project form of each plugin skill file, at every
|
|
4
|
+
* 3.0.x tag). Those versions wrote no install manifest, so a folder whose every file
|
|
5
|
+
* is listed here is SpecWeave's unmodified copy; anything else is the user's.
|
|
6
|
+
* Generated once from the release tags; 3.1 and later record a manifest instead.
|
|
7
|
+
*
|
|
8
|
+
* @module core/skills/legacy-skill-hashes
|
|
9
|
+
*/
|
|
10
|
+
export declare const LEGACY_SKILL_HASHES: Readonly<Record<string, Readonly<Record<string, readonly string[]>>>>;
|
|
11
|
+
//# sourceMappingURL=legacy-skill-hashes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"legacy-skill-hashes.d.ts","sourceRoot":"","sources":["../../../../src/core/skills/legacy-skill-hashes.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC,CAwErG,CAAC"}
|