harness-everything 0.12.3 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.github/workflows/ci.yml +2 -0
- package/CHANGELOG.md +30 -0
- package/ci/mechanism-2aj-waza-reference-adapter.test.js +223 -0
- package/ci/mechanism-2ak-contract-probe-adapter.test.js +207 -0
- package/ci/openai-plugin-package.test.js +10 -0
- package/contract-integrity/ADAPTERS.md +37 -0
- package/contract-integrity/fixtures/node-probe-project/package.json +8 -0
- package/contract-integrity/fixtures/node-probe-project/probe-plan.json +27 -0
- package/contract-integrity/fixtures/node-probe-project/src/limit.js +8 -0
- package/contract-integrity/fixtures/node-probe-project/test/contract.test.js +14 -0
- package/contract-integrity/scripts/node-probe-adapter.js +390 -0
- package/docs/repository-contract.md +1 -1
- package/evals/multi-agent-workspace/tasks/positive-trigger.yaml +1 -1
- package/fable-mode/SKILL.md +1 -1
- package/fable-mode/execution-guardrails/SKILL.md +1 -1
- package/fable-mode/fable-haiku/SKILL.md +1 -1
- package/fable-mode/fable-opus/SKILL.md +1 -1
- package/fable-mode/fable-sonnet/SKILL.md +1 -1
- package/fable-mode/references/model-matrix.md +2 -0
- package/harness-everything/SKILL.md +1 -1
- package/harness-everything/references/triage-and-tiers.md +2 -0
- package/multi-agent-workspace/SKILL.md +2 -2
- package/multi-agent-workspace/references/orchestration.md +2 -0
- package/opencode-plugin/plugin.json +1 -1
- package/package.json +4 -2
- package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
- package/plugins/harness-everything/contract-integrity/ADAPTERS.md +37 -0
- package/plugins/harness-everything/contract-integrity/scripts/node-probe-adapter.js +390 -0
- package/plugins/harness-everything/plugin.json +1 -1
- package/plugins/harness-everything/skills/fable-mode/SKILL.md +1 -1
- package/plugins/harness-everything/skills/fable-mode/execution-guardrails/SKILL.md +1 -1
- package/plugins/harness-everything/skills/fable-mode/fable-haiku/SKILL.md +1 -1
- package/plugins/harness-everything/skills/fable-mode/fable-opus/SKILL.md +1 -1
- package/plugins/harness-everything/skills/fable-mode/fable-sonnet/SKILL.md +1 -1
- package/plugins/harness-everything/skills/fable-mode/references/model-matrix.md +2 -0
- package/plugins/harness-everything/skills/harness-everything/SKILL.md +1 -1
- package/plugins/harness-everything/skills/harness-everything/references/triage-and-tiers.md +2 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/SKILL.md +2 -2
- package/plugins/harness-everything/skills/multi-agent-workspace/references/orchestration.md +2 -0
- package/plugins/harness-everything/skills/skill-creator/SKILL.md +1 -1
- package/plugins/harness-everything/skills/skill-creator/references/quality-checklist.md +2 -0
- package/plugins/harness-everything/skills/tdd/SKILL.md +1 -1
- package/plugins/harness-everything/skills/tdd/references/core-discipline.md +1 -1
- package/plugins/harness-everything/skills/tdd/references/quality-model.md +2 -2
- package/scripts/sync-openai-plugin.js +4 -1
- package/scripts/waza-harness-check.js +361 -0
- package/skill-creator/SKILL.md +1 -1
- package/skill-creator/references/quality-checklist.md +2 -0
- package/tdd/SKILL.md +1 -1
- package/tdd/references/core-discipline.md +1 -1
- package/tdd/references/quality-model.md +2 -2
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { spawnSync } = require('child_process');
|
|
7
|
+
const {
|
|
8
|
+
discoverSkills,
|
|
9
|
+
extractCandidates,
|
|
10
|
+
classifyReference,
|
|
11
|
+
checkSkillReferences,
|
|
12
|
+
} = require('../ci/reference-check');
|
|
13
|
+
|
|
14
|
+
const DEFAULT_ROOT = path.resolve(__dirname, '..');
|
|
15
|
+
const READY_COMPLIANCE = new Set(['Medium-High', 'High']);
|
|
16
|
+
|
|
17
|
+
function slash(value) {
|
|
18
|
+
return String(value || '').replace(/\\/g, '/');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function decodeTarget(value) {
|
|
22
|
+
const raw = String(value || '');
|
|
23
|
+
try { return decodeURIComponent(raw); }
|
|
24
|
+
catch (_) { return raw; }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function unwrapMarkdownDestination(value) {
|
|
28
|
+
const target = String(value || '').trim();
|
|
29
|
+
return target.startsWith('<') && target.endsWith('>') ? target.slice(1, -1) : target;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function markdownLinkTargets(text) {
|
|
33
|
+
const targets = [];
|
|
34
|
+
for (const match of String(text || '').matchAll(/\[[^\]]*\]\(([^)]+)\)/g)) {
|
|
35
|
+
const target = unwrapMarkdownDestination(match[1]);
|
|
36
|
+
if (target) targets.push(target);
|
|
37
|
+
}
|
|
38
|
+
return targets;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function harnessReachableReferenceFiles(root, skillName) {
|
|
42
|
+
const skillRoot = path.join(root, skillName);
|
|
43
|
+
const rootKey = path.resolve(skillRoot);
|
|
44
|
+
const queue = ['SKILL.md'];
|
|
45
|
+
const visited = new Set();
|
|
46
|
+
const reachable = new Set();
|
|
47
|
+
|
|
48
|
+
while (queue.length) {
|
|
49
|
+
const relativeDoc = slash(queue.shift());
|
|
50
|
+
if (visited.has(relativeDoc)) continue;
|
|
51
|
+
visited.add(relativeDoc);
|
|
52
|
+
|
|
53
|
+
const full = path.resolve(skillRoot, relativeDoc);
|
|
54
|
+
if (!full.startsWith(rootKey + path.sep) && full !== rootKey) continue;
|
|
55
|
+
if (!fs.existsSync(full) || !fs.statSync(full).isFile()) continue;
|
|
56
|
+
|
|
57
|
+
const text = fs.readFileSync(full, 'utf8');
|
|
58
|
+
const docDir = path.dirname(full);
|
|
59
|
+
|
|
60
|
+
for (const reference of extractCandidates(text)) {
|
|
61
|
+
const classified = classifyReference(root, skillName, reference);
|
|
62
|
+
if (classified.status !== 'check' || !fs.existsSync(classified.target)) continue;
|
|
63
|
+
const relToSkill = slash(path.relative(skillRoot, classified.target));
|
|
64
|
+
if (!relToSkill.startsWith('../') && relToSkill !== '..') {
|
|
65
|
+
if (relToSkill.startsWith('references/')) reachable.add(relToSkill);
|
|
66
|
+
if (/\.mdx?$/i.test(relToSkill)) queue.push(relToSkill);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
for (const targetRaw of markdownLinkTargets(text)) {
|
|
71
|
+
const target = decodeTarget(targetRaw).split('#', 1)[0];
|
|
72
|
+
if (!target || /^(?:https?:|mailto:|mdc:)/i.test(target)) continue;
|
|
73
|
+
if (/^<[^>]+>\//.test(target)) {
|
|
74
|
+
const classified = classifyReference(root, skillName, target);
|
|
75
|
+
if (classified.status !== 'check' || !fs.existsSync(classified.target)) continue;
|
|
76
|
+
const relToSkill = slash(path.relative(skillRoot, classified.target));
|
|
77
|
+
if (!relToSkill.startsWith('../') && relToSkill !== '..') {
|
|
78
|
+
if (relToSkill.startsWith('references/')) reachable.add(relToSkill);
|
|
79
|
+
if (/\.mdx?$/i.test(relToSkill)) queue.push(relToSkill);
|
|
80
|
+
}
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const resolved = path.resolve(docDir, target);
|
|
84
|
+
const relToSkill = slash(path.relative(skillRoot, resolved));
|
|
85
|
+
if (relToSkill === '..' || relToSkill.startsWith('../')) continue;
|
|
86
|
+
if (!fs.existsSync(resolved) || !fs.statSync(resolved).isFile()) continue;
|
|
87
|
+
if (relToSkill.startsWith('references/')) reachable.add(relToSkill);
|
|
88
|
+
if (/\.mdx?$/i.test(relToSkill)) queue.push(relToSkill);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return reachable;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function classifyReportedLink(root, skillName, issue) {
|
|
95
|
+
const target = unwrapMarkdownDestination(decodeTarget(issue && issue.target));
|
|
96
|
+
if (!target) return { category: 'local', issue };
|
|
97
|
+
const source = slash(issue && issue.source || 'SKILL.md');
|
|
98
|
+
|
|
99
|
+
// Files under templates/ describe paths that exist only after generation.
|
|
100
|
+
// Keep Waza's finding visible, but do not confuse template output paths with
|
|
101
|
+
// broken links in the checked-in skill itself.
|
|
102
|
+
if (source === 'templates' || source.startsWith('templates/')) {
|
|
103
|
+
return { category: 'template', issue: { ...issue, target } };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (/^<[^>]+>\//.test(target)) {
|
|
107
|
+
const classified = classifyReference(root, skillName, target);
|
|
108
|
+
if (classified.status === 'skip') {
|
|
109
|
+
return { category: 'template', issue: { ...issue, target } };
|
|
110
|
+
}
|
|
111
|
+
if (classified.status === 'check' && fs.existsSync(classified.target)) {
|
|
112
|
+
return {
|
|
113
|
+
category: 'resolved-contract',
|
|
114
|
+
issue: { ...issue, target, resolved: slash(path.relative(root, classified.target)) },
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Waza intentionally treats links that escape a skill directory as scope
|
|
120
|
+
// findings. Harness permits those links only when they resolve to a real
|
|
121
|
+
// checked-in path inside this repository. Missing/outside paths remain hard
|
|
122
|
+
// local failures.
|
|
123
|
+
const targetPath = target.split('#', 1)[0];
|
|
124
|
+
if (/^\.\.?(?:\/|$)/.test(targetPath)) {
|
|
125
|
+
const repoRoot = path.resolve(root);
|
|
126
|
+
const sourceFile = path.resolve(repoRoot, skillName, source);
|
|
127
|
+
const resolved = path.resolve(path.dirname(sourceFile), targetPath);
|
|
128
|
+
const relative = path.relative(repoRoot, resolved);
|
|
129
|
+
const insideRepo = relative !== '..' && !relative.startsWith('..' + path.sep) && !path.isAbsolute(relative);
|
|
130
|
+
if (insideRepo && fs.existsSync(resolved)) {
|
|
131
|
+
return {
|
|
132
|
+
category: 'resolved-contract',
|
|
133
|
+
issue: { ...issue, target, resolved: slash(relative) },
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return { category: 'local', issue: { ...issue, target } };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function nonLinkFailureReasons(skill) {
|
|
142
|
+
const reasons = [];
|
|
143
|
+
const compliance = skill?.compliance?.level;
|
|
144
|
+
if (!READY_COMPLIANCE.has(compliance)) reasons.push('compliance:' + (compliance || 'missing'));
|
|
145
|
+
if (skill?.tokenBudget?.exceeded === true) reasons.push('token-budget-exceeded');
|
|
146
|
+
for (const item of skill?.specCompliance || []) {
|
|
147
|
+
if (item.passed === false) reasons.push('spec:' + (item.name || 'unnamed'));
|
|
148
|
+
}
|
|
149
|
+
if (skill?.schema && skill.schema.valid === false) reasons.push('schema-invalid');
|
|
150
|
+
return reasons;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function nonLinkReady(skill) {
|
|
154
|
+
return nonLinkFailureReasons(skill).length === 0;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function normalizeSkillReport(root, skill) {
|
|
158
|
+
const skillName = skill.name || path.basename(skill.path || '');
|
|
159
|
+
const links = skill.links || {};
|
|
160
|
+
const reachable = harnessReachableReferenceFiles(root, skillName);
|
|
161
|
+
|
|
162
|
+
const resolvedPlaceholderOrphans = [];
|
|
163
|
+
const remainingOrphanedFiles = [];
|
|
164
|
+
for (const orphan of links.orphanedFiles || []) {
|
|
165
|
+
const normalized = slash(orphan);
|
|
166
|
+
if (reachable.has(normalized)) resolvedPlaceholderOrphans.push(normalized);
|
|
167
|
+
else remainingOrphanedFiles.push(normalized);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const resolvedContractLinks = [];
|
|
171
|
+
const templateAdvisories = [];
|
|
172
|
+
const remainingLocalLinkIssues = [];
|
|
173
|
+
for (const [kind, items] of [
|
|
174
|
+
['broken', links.brokenLinks || []],
|
|
175
|
+
['directory', links.directoryLinks || []],
|
|
176
|
+
['scope', links.scopeEscapes || []],
|
|
177
|
+
]) {
|
|
178
|
+
for (const issue of items) {
|
|
179
|
+
const classified = classifyReportedLink(root, skillName, issue);
|
|
180
|
+
const entry = { kind, ...classified.issue };
|
|
181
|
+
if (classified.category === 'resolved-contract') resolvedContractLinks.push(entry);
|
|
182
|
+
else if (classified.category === 'template') templateAdvisories.push(entry);
|
|
183
|
+
else remainingLocalLinkIssues.push(entry);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const networkAdvisories = (links.deadURLs || []).map(issue => ({ kind: 'network', ...issue }));
|
|
188
|
+
const localLinksPassed = remainingLocalLinkIssues.length === 0 && remainingOrphanedFiles.length === 0;
|
|
189
|
+
const harnessReady = nonLinkReady(skill) && localLinksPassed;
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
...skill,
|
|
193
|
+
harnessCompatibility: {
|
|
194
|
+
wazaReady: Boolean(skill.ready),
|
|
195
|
+
harnessReady,
|
|
196
|
+
localLinksPassed,
|
|
197
|
+
nonLinkFailures: nonLinkFailureReasons(skill),
|
|
198
|
+
resolvedPlaceholderOrphans,
|
|
199
|
+
resolvedContractLinks,
|
|
200
|
+
templateAdvisories,
|
|
201
|
+
networkAdvisories,
|
|
202
|
+
remainingLocalLinkIssues,
|
|
203
|
+
remainingOrphanedFiles,
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function normalizeReport(root, report) {
|
|
209
|
+
return {
|
|
210
|
+
...report,
|
|
211
|
+
harnessReferenceContract: {
|
|
212
|
+
version: 1,
|
|
213
|
+
resolvablePlaceholders: ['this-skill-dir', 'skills-repo-root'],
|
|
214
|
+
genericPlaceholders: ['workspace', 'skill', 'kebab-case-name'],
|
|
215
|
+
},
|
|
216
|
+
skills: (report.skills || []).map(skill => normalizeSkillReport(root, skill)),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function parseArgs(argv) {
|
|
221
|
+
const args = { root: DEFAULT_ROOT, waza: process.env.WAZA_BIN || 'waza', format: 'text' };
|
|
222
|
+
for (let i = 0; i < argv.length; i++) {
|
|
223
|
+
const value = argv[i];
|
|
224
|
+
if (value === '--root') args.root = path.resolve(argv[++i]);
|
|
225
|
+
else if (value === '--waza') args.waza = argv[++i];
|
|
226
|
+
else if (value === '--format') args.format = argv[++i];
|
|
227
|
+
else if (value === '--help' || value === '-h') args.help = true;
|
|
228
|
+
else throw new Error('unknown argument: ' + value);
|
|
229
|
+
}
|
|
230
|
+
if (!['text', 'json'].includes(args.format)) throw new Error('--format must be text or json');
|
|
231
|
+
return args;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function printText(report) {
|
|
235
|
+
let harnessReady = 0;
|
|
236
|
+
let localPass = 0;
|
|
237
|
+
let resolvedOrphans = 0;
|
|
238
|
+
let network = 0;
|
|
239
|
+
let templates = 0;
|
|
240
|
+
for (const skill of report.skills || []) {
|
|
241
|
+
const h = skill.harnessCompatibility;
|
|
242
|
+
if (h.harnessReady) harnessReady++;
|
|
243
|
+
if (h.localLinksPassed) localPass++;
|
|
244
|
+
resolvedOrphans += h.resolvedPlaceholderOrphans.length;
|
|
245
|
+
network += h.networkAdvisories.length;
|
|
246
|
+
templates += h.templateAdvisories.length;
|
|
247
|
+
const icon = h.localLinksPassed ? 'PASS' : 'FAIL';
|
|
248
|
+
process.stdout.write(
|
|
249
|
+
`${icon} ${skill.name}: local-links=${h.localLinksPassed ? 'ok' : 'broken'}` +
|
|
250
|
+
` harness-ready=${h.harnessReady ? 'yes' : 'no'}` +
|
|
251
|
+
` nonlink=${h.nonLinkFailures.length ? h.nonLinkFailures.join(',') : 'ok'}` +
|
|
252
|
+
` resolved-placeholder-orphans=${h.resolvedPlaceholderOrphans.length}` +
|
|
253
|
+
` network-advisories=${h.networkAdvisories.length}` +
|
|
254
|
+
` template-advisories=${h.templateAdvisories.length}\n`
|
|
255
|
+
);
|
|
256
|
+
for (const issue of h.remainingLocalLinkIssues) {
|
|
257
|
+
process.stdout.write(` LOCAL ${issue.kind}: ${issue.source || 'SKILL.md'} -> ${issue.target} (${issue.reason || 'unresolved'})\n`);
|
|
258
|
+
}
|
|
259
|
+
for (const orphan of h.remainingOrphanedFiles) {
|
|
260
|
+
process.stdout.write(` ORPHAN ${orphan}\n`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
const total = (report.skills || []).length;
|
|
264
|
+
process.stdout.write(
|
|
265
|
+
`Harness/Waza compatibility: local-link-pass ${localPass}/${total}; ` +
|
|
266
|
+
`harness-ready ${harnessReady}/${total}; resolved false orphan advisories ${resolvedOrphans}; ` +
|
|
267
|
+
`network advisories ${network}; template advisories ${templates}.\n`
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function runCanonicalWazaChecks(root, waza, spawn = spawnSync) {
|
|
272
|
+
const skillNames = discoverSkills(root);
|
|
273
|
+
const skills = [];
|
|
274
|
+
let timestamp = null;
|
|
275
|
+
|
|
276
|
+
for (const skillName of skillNames) {
|
|
277
|
+
const skillPath = path.join(root, skillName);
|
|
278
|
+
const result = spawn(waza, ['check', skillPath, '--format', 'json'], {
|
|
279
|
+
cwd: root,
|
|
280
|
+
encoding: 'utf8',
|
|
281
|
+
windowsHide: true,
|
|
282
|
+
env: { ...process.env, WAZA_NO_UPDATE_CHECK: '1' },
|
|
283
|
+
});
|
|
284
|
+
if (result.error) {
|
|
285
|
+
throw new Error('cannot execute waza for ' + skillName + ': ' + result.error.message);
|
|
286
|
+
}
|
|
287
|
+
if (result.status !== 0) {
|
|
288
|
+
const detail = String(result.stderr || result.stdout || '').trim();
|
|
289
|
+
throw new Error('waza check failed for ' + skillName + ' with exit ' + result.status +
|
|
290
|
+
(detail ? ': ' + detail : ''));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
let raw;
|
|
294
|
+
try {
|
|
295
|
+
raw = JSON.parse(result.stdout);
|
|
296
|
+
} catch (error) {
|
|
297
|
+
throw new Error('invalid waza JSON output for ' + skillName + ': ' + error.message);
|
|
298
|
+
}
|
|
299
|
+
if (!Array.isArray(raw.skills) || raw.skills.length !== 1) {
|
|
300
|
+
throw new Error('waza check for ' + skillName + ' did not return exactly one skill report');
|
|
301
|
+
}
|
|
302
|
+
timestamp = timestamp || raw.timestamp || null;
|
|
303
|
+
skills.push(raw.skills[0]);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
timestamp: timestamp || new Date().toISOString(),
|
|
308
|
+
skills,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function main() {
|
|
313
|
+
let args;
|
|
314
|
+
try {
|
|
315
|
+
args = parseArgs(process.argv.slice(2));
|
|
316
|
+
} catch (error) {
|
|
317
|
+
console.error('[Waza Harness Adapter] ' + error.message);
|
|
318
|
+
process.exit(2);
|
|
319
|
+
}
|
|
320
|
+
if (args.help) {
|
|
321
|
+
console.log('Usage: node scripts/waza-harness-check.js [--root <repo>] [--waza <binary>] [--format text|json]');
|
|
322
|
+
process.exit(0);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const canonical = checkSkillReferences(args.root);
|
|
326
|
+
if (canonical.failures.length) {
|
|
327
|
+
for (const failure of canonical.failures) {
|
|
328
|
+
console.error(`[Waza Harness Adapter] unresolved Harness reference: ${failure.skill} -> ${failure.reference} (${failure.target})`);
|
|
329
|
+
}
|
|
330
|
+
process.exit(1);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
let raw;
|
|
334
|
+
try {
|
|
335
|
+
raw = runCanonicalWazaChecks(args.root, args.waza);
|
|
336
|
+
} catch (error) {
|
|
337
|
+
console.error('[Waza Harness Adapter] ' + error.message);
|
|
338
|
+
process.exit(2);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const normalized = normalizeReport(args.root, raw);
|
|
342
|
+
if (args.format === 'json') process.stdout.write(JSON.stringify(normalized, null, 2) + '\n');
|
|
343
|
+
else printText(normalized);
|
|
344
|
+
|
|
345
|
+
const failed = normalized.skills.some(skill => !skill.harnessCompatibility.harnessReady);
|
|
346
|
+
process.exit(failed ? 1 : 0);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (require.main === module) main();
|
|
350
|
+
|
|
351
|
+
module.exports = {
|
|
352
|
+
unwrapMarkdownDestination,
|
|
353
|
+
markdownLinkTargets,
|
|
354
|
+
harnessReachableReferenceFiles,
|
|
355
|
+
classifyReportedLink,
|
|
356
|
+
nonLinkFailureReasons,
|
|
357
|
+
nonLinkReady,
|
|
358
|
+
normalizeSkillReport,
|
|
359
|
+
normalizeReport,
|
|
360
|
+
runCanonicalWazaChecks,
|
|
361
|
+
};
|
package/skill-creator/SKILL.md
CHANGED
|
@@ -14,3 +14,5 @@ A skill isn't done until every line here is true. This is deliberately a flat ch
|
|
|
14
14
|
- [ ] **Reuses existing leading words** (`Tier 1/2/3`, `circuit breaker`/`zoom out`, `Rule of 3`, `Red/Green/Refactor`, `Discover > Think > Try > Summarize > Record`) instead of naming a new concept for something this repo already has a word for.
|
|
15
15
|
- [ ] **Completion criteria are checkable** — tied to an exit code, a file's existence, a grep result, or explicit human confirmation, not "when it feels done."
|
|
16
16
|
- [ ] **Description reads as a human-facing sentence**, not just a trigger-phrase list — `scripts/installer.js`'s interactive picker prints it next to a checkbox for a person deciding whether to install this skill; that's its real first reader in this architecture (audit report §2.1).
|
|
17
|
+
|
|
18
|
+
Related guides: [authoring workflow](authoring-workflow.md), [testing workflow](testing-workflow.md), [quality principles](quality-principles.md), and [dynamic generation contract](dynamic-generation-contract.md).
|
package/tdd/SKILL.md
CHANGED
|
@@ -22,7 +22,7 @@ flowchart TD
|
|
|
22
22
|
|
|
23
23
|
When this skill is loaded, you MUST suppress the urge to write implementation code directly, and strictly follow these three phases.
|
|
24
24
|
|
|
25
|
-
For public behavior, RED/GREEN evidence is necessary but not sufficient. Apply the machine-verifiable matrix, source-conformance policy, determinism comparison, and mandatory gates in
|
|
25
|
+
For public behavior, RED/GREEN evidence is necessary but not sufficient. Apply the machine-verifiable matrix, source-conformance policy, determinism comparison, and mandatory gates in the [quality model](quality-model.md) before completion.
|
|
26
26
|
|
|
27
27
|
**Evidence Assertion (Law of Evidence Assertion - 證據斷言定律)**:
|
|
28
28
|
During each TDD phase (RED, GREEN, REFACTOR), you MUST present actual terminal test execution logs showing the exact test failure or pass. Claiming progress or success based on "assumed" passes is strictly PROHIBITED; you must assert outcomes with hard, objective evidence.
|
|
@@ -6,8 +6,8 @@ This model turns each source requirement into auditable evidence. The authoritat
|
|
|
6
6
|
|
|
7
7
|
Every requirement declares exactly one `testProfile`: `unit` or `integration`. The selected profile defines which case classes are applicable; the common scoring and source rules do not make every test use identical evidence.
|
|
8
8
|
|
|
9
|
-
- `unit`: follow
|
|
10
|
-
- `integration`: follow
|
|
9
|
+
- `unit`: follow [unit testing](unit-testing.md). Integration determinism is N/A with a written reason.
|
|
10
|
+
- `integration`: follow [integration testing](integration-testing.md). Determinism is mandatory and cannot be declared N/A.
|
|
11
11
|
|
|
12
12
|
When one task needs both profiles, split it into separate requirement records so each result remains measurable.
|
|
13
13
|
|