harness-everything 0.32.0 → 0.34.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 +2 -1
- package/CHANGELOG.md +33 -0
- package/README.md +3 -2
- package/ci/fixtures/doc-lifecycle/prd-revisions/config.json +9 -0
- package/ci/fixtures/doc-lifecycle/prd-revisions/packaged/config.json +20 -0
- package/ci/fixtures/doc-lifecycle/prd-revisions/packaged/expected.json +12 -0
- package/ci/fixtures/doc-lifecycle/prd-revisions/packaged/invalid-fork/docs/prd/graph/r001.md +20 -0
- package/ci/fixtures/doc-lifecycle/prd-revisions/packaged/invalid-fork/docs/prd/graph/r002.md +26 -0
- package/ci/fixtures/doc-lifecycle/prd-revisions/packaged/invalid-fork/docs/prd/graph/r003.md +26 -0
- package/ci/fixtures/doc-lifecycle/prd-revisions/packaged/valid/docs/prd/graph/r001.md +20 -0
- package/ci/fixtures/doc-lifecycle/prd-revisions/packaged/valid/docs/prd/graph/r002.md +26 -0
- package/ci/fixtures/doc-lifecycle/prd-revisions/ten-reqs/r001.body.md +39 -0
- package/ci/fixtures/doc-lifecycle/prd-revisions/ten-reqs/r002-dropped.body.md +35 -0
- package/ci/fixtures/doc-lifecycle/prd-revisions/ten-reqs/r002-incomplete.body.md +37 -0
- package/ci/fixtures/doc-lifecycle/prd-revisions/ten-reqs/r002-one-replaced.body.md +39 -0
- package/ci/fixtures/doc-lifecycle/prd-revisions/ten-reqs/r002-overlap.body.md +39 -0
- package/ci/fixtures/doc-lifecycle/scenarios/active-to-active/config.json +1 -0
- package/ci/fixtures/doc-lifecycle/scenarios/active-to-active/docs/adr/GRPH-001.md +8 -0
- package/ci/fixtures/doc-lifecycle/scenarios/active-to-active/docs/adr/GRPH-002.md +9 -0
- package/ci/fixtures/doc-lifecycle/scenarios/active-to-active/expected.json +1 -0
- package/ci/fixtures/doc-lifecycle/scenarios/active-to-active-mismatch/config.json +1 -0
- package/ci/fixtures/doc-lifecycle/scenarios/active-to-active-mismatch/docs/adr/GRPH-001.md +8 -0
- package/ci/fixtures/doc-lifecycle/scenarios/active-to-active-mismatch/docs/adr/GRPH-002.md +9 -0
- package/ci/fixtures/doc-lifecycle/scenarios/active-to-active-mismatch/expected.json +1 -0
- package/ci/fixtures/doc-lifecycle/scenarios/active-to-legacy/config.json +1 -0
- package/ci/fixtures/doc-lifecycle/scenarios/active-to-legacy/docs/adr/GRPH-010.md +9 -0
- package/ci/fixtures/doc-lifecycle/scenarios/active-to-legacy/docs/adr/legacy/GRPH-007.md +8 -0
- package/ci/fixtures/doc-lifecycle/scenarios/active-to-legacy/expected.json +1 -0
- package/ci/fixtures/doc-lifecycle/scenarios/alias-rename/config.json +1 -0
- package/ci/fixtures/doc-lifecycle/scenarios/alias-rename/docs/adr/GRAPH-007.md +9 -0
- package/ci/fixtures/doc-lifecycle/scenarios/alias-rename/docs/adr/legacy/GRPH-007.md +8 -0
- package/ci/fixtures/doc-lifecycle/scenarios/alias-rename/expected.json +1 -0
- package/ci/fixtures/doc-lifecycle/scenarios/duplicate-legacy/config.json +1 -0
- package/ci/fixtures/doc-lifecycle/scenarios/duplicate-legacy/docs/adr/legacy/v1/GRPH-007.md +8 -0
- package/ci/fixtures/doc-lifecycle/scenarios/duplicate-legacy/docs/adr/legacy/v2/GRPH-007.md +8 -0
- package/ci/fixtures/doc-lifecycle/scenarios/duplicate-legacy/expected.json +1 -0
- package/ci/fixtures/doc-lifecycle/scenarios/invalid-refs/config.json +1 -0
- package/ci/fixtures/doc-lifecycle/scenarios/invalid-refs/docs/adr/GRPH-003.md +9 -0
- package/ci/fixtures/doc-lifecycle/scenarios/invalid-refs/docs/adr/GRPH-004.md +9 -0
- package/ci/fixtures/doc-lifecycle/scenarios/invalid-refs/docs/adr/GRPH-005.md +9 -0
- package/ci/fixtures/doc-lifecycle/scenarios/invalid-refs/docs/adr/GRPH-006.md +9 -0
- package/ci/fixtures/doc-lifecycle/scenarios/invalid-refs/docs/adr/GRPH-007.md +9 -0
- package/ci/fixtures/doc-lifecycle/scenarios/invalid-refs/expected.json +1 -0
- package/ci/fixtures/doc-lifecycle/scenarios/pending-edge/config.json +1 -0
- package/ci/fixtures/doc-lifecycle/scenarios/pending-edge/docs/adr/GRPH-020.md +8 -0
- package/ci/fixtures/doc-lifecycle/scenarios/pending-edge/docs/adr/GRPH-021.md +9 -0
- package/ci/fixtures/doc-lifecycle/scenarios/pending-edge/expected.json +1 -0
- package/ci/fixtures/doc-lifecycle/scenarios/recorded-conflict/config.json +1 -0
- package/ci/fixtures/doc-lifecycle/scenarios/recorded-conflict/docs/adr/GRPH-003.md +8 -0
- package/ci/fixtures/doc-lifecycle/scenarios/recorded-conflict/docs/prd/graph.md +10 -0
- package/ci/fixtures/doc-lifecycle/scenarios/recorded-conflict/expected.json +1 -0
- package/ci/mechanism-2aw-project-docs-config.test.js +759 -0
- package/ci/mechanism-2ax-doc-lifecycle.test.js +1061 -0
- package/ci/mechanism-2ay-prd-revisions.test.js +2891 -0
- package/ci/mechanism-2az-step-type-registry.test.js +86 -0
- package/ci/openai-plugin-package.test.js +1 -1
- package/ci/release-consistency-check.js +26 -9
- package/ci/release-consistency-check.test.js +30 -0
- package/ci/router-workflow-plan.test.js +36 -0
- package/code-review/SKILL.md +41 -0
- package/code-review/references/review-playbook.md +40 -0
- package/code-review/templates/finding.template.md +33 -0
- package/docs/document-lifecycle.md +206 -0
- package/docs/openai-plugin.md +1 -1
- package/docs/workflows/code-review.md +58 -0
- package/evals/code-review/eval.yaml +26 -0
- package/evals/code-review/tasks/negative-grill-me.yaml +16 -0
- package/evals/code-review/tasks/negative-security-review.yaml +16 -0
- package/evals/code-review/tasks/negative-tdd.yaml +16 -0
- package/evals/code-review/tasks/negative-verification-loop.yaml +16 -0
- package/evals/code-review/tasks/positive-pr-review.yaml +16 -0
- package/evals/code-review/tasks/positive-revised-head-rereview.yaml +16 -0
- package/harness-everything/SKILL.md +1 -1
- package/harness-everything/references/skill-registry.md +1 -0
- package/harness-everything/scripts/doc-lifecycle.js +2401 -0
- package/harness-everything/scripts/project-docs-resolver.js +764 -0
- package/harness-everything/scripts/routing-keywords.json +7 -0
- package/hooks/scripts/lib/binding-resolver.js +1 -0
- package/multi-agent-workspace/SKILL.md +1 -1
- package/multi-agent-workspace/scripts/project-docs-resolver.js +437 -30
- package/opencode-plugin/plugin.json +1 -1
- package/package.json +2 -2
- package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
- package/plugins/harness-everything/hooks/scripts/lib/binding-resolver.js +1 -0
- package/plugins/harness-everything/plugin.json +1 -1
- package/plugins/harness-everything/skills/code-review/SKILL.md +41 -0
- package/plugins/harness-everything/skills/code-review/references/review-playbook.md +40 -0
- package/plugins/harness-everything/skills/code-review/templates/finding.template.md +33 -0
- package/plugins/harness-everything/skills/harness-everything/SKILL.md +1 -1
- package/plugins/harness-everything/skills/harness-everything/references/skill-registry.md +1 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/doc-lifecycle.js +2401 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/project-docs-resolver.js +764 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/routing-keywords.json +7 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/SKILL.md +1 -1
- package/plugins/harness-everything/skills/multi-agent-workspace/scripts/project-docs-resolver.js +437 -30
- package/plugins/harness-everything/skills/to-spec/SKILL.md +1 -1
- package/plugins/harness-everything/skills/to-spec/scripts/check-project-docs.js +168 -54
- package/plugins/harness-everything/skills/to-spec/scripts/project-docs-resolver.js +749 -96
- package/sbom.cdx.json +7 -7
- package/to-spec/SKILL.md +1 -1
- package/to-spec/scripts/check-project-docs.js +168 -54
- package/to-spec/scripts/project-docs-resolver.js +749 -96
|
@@ -832,6 +832,92 @@ checkCase('Claude hook copy is byte-identical and a tracked Codex runtime includ
|
|
|
832
832
|
compat.uninstall({ codexHome });
|
|
833
833
|
});
|
|
834
834
|
|
|
835
|
+
checkCase('registered code-review optional binding activates review steps from source and packaged roots', () => {
|
|
836
|
+
const requirements = [
|
|
837
|
+
{ id: 'req-review', stepType: 'review', summary: 'Review the change', acceptance: 'findings recorded with evidence tiers', optionalBindings: ['code-review'] },
|
|
838
|
+
{ id: 'req-verify', stepType: 'verification', summary: 'Verify the change', acceptance: 'objective checks pass' },
|
|
839
|
+
];
|
|
840
|
+
for (const leg of [
|
|
841
|
+
{ root: ROOT, sessionId: 'code-review-source-root', expected: path.join(ROOT, 'code-review', 'SKILL.md') },
|
|
842
|
+
{ root: path.join(tempRoot, 'code-review-packaged-root'), sessionId: 'code-review-packaged-root', expected: path.join(tempRoot, 'code-review-packaged-root', 'skills', 'code-review', 'SKILL.md') },
|
|
843
|
+
]) {
|
|
844
|
+
if (leg.root !== ROOT) {
|
|
845
|
+
fs.mkdirSync(leg.root, { recursive: true });
|
|
846
|
+
for (const directory of ['hooks', 'harness-everything', 'scripts', 'fable-mode']) {
|
|
847
|
+
fs.cpSync(path.join(ROOT, directory), path.join(leg.root, directory), { recursive: true });
|
|
848
|
+
}
|
|
849
|
+
fs.cpSync(path.join(packageRoot, 'skills'), path.join(leg.root, 'skills'), { recursive: true });
|
|
850
|
+
}
|
|
851
|
+
const session = planSession(leg.sessionId, requirements, 'iterative-single', leg.root, FIX_PROMPT, 'author-declared plan with an optional code-review binding', leg.root);
|
|
852
|
+
assert.strictEqual(session.planned.status, 0, session.planned.stderr);
|
|
853
|
+
const declared = readJson(session.obligationsFile).requirements[0].optionalBindings.find(item => item.id === 'code-review');
|
|
854
|
+
assert.strictEqual(declared.availability, 'available', 'the registered optional binding must resolve from the declared runtime root');
|
|
855
|
+
assert.strictEqual(fs.realpathSync(declared.resolvedPath), fs.realpathSync(leg.expected), 'the optional binding resolves to the canonical code-review skill file');
|
|
856
|
+
const legController = path.join(leg.root, 'hooks/scripts/workflow-disposition.js');
|
|
857
|
+
const started = run(legController, ['start', '--session-id', leg.sessionId], leg.root);
|
|
858
|
+
assert.strictEqual(started.status, 0, started.stderr);
|
|
859
|
+
const load = run(legController, [
|
|
860
|
+
'binding', '--session-id', leg.sessionId, '--step-id', 'req-review', '--binding-id', 'code-review',
|
|
861
|
+
'--disposition', 'loaded', '--evidence', 'Read code-review/SKILL.md and applied its evidence-tier rubric.',
|
|
862
|
+
], leg.root);
|
|
863
|
+
assert.strictEqual(load.status, 0, load.stderr);
|
|
864
|
+
const loaded = readJson(session.obligationsFile).requirements[0].optionalBindings.find(item => item.id === 'code-review');
|
|
865
|
+
assert.strictEqual(loaded.status, 'loaded');
|
|
866
|
+
assert.strictEqual(fs.realpathSync(loaded.resolvedPath), fs.realpathSync(leg.expected), 'the loaded binding keeps its canonical resolved path');
|
|
867
|
+
const passed = run(legController, [
|
|
868
|
+
'step', '--session-id', leg.sessionId, '--step-id', 'req-review', '--disposition', 'pass',
|
|
869
|
+
'--evidence', 'Review findings filed against the diff with evidence tiers.',
|
|
870
|
+
], leg.root);
|
|
871
|
+
assert.strictEqual(passed.status, 0, passed.stderr);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
const notNeeded = planSession('code-review-not-needed', requirements, 'iterative-single', ROOT, FIX_PROMPT, 'author-declared plan marks code-review not needed');
|
|
875
|
+
assert.strictEqual(notNeeded.planned.status, 0, notNeeded.planned.stderr);
|
|
876
|
+
assert.strictEqual(run(controller, ['start', '--session-id', 'code-review-not-needed'], ROOT).status, 0);
|
|
877
|
+
assert.strictEqual(run(controller, [
|
|
878
|
+
'binding', '--session-id', 'code-review-not-needed', '--step-id', 'req-review', '--binding-id', 'code-review',
|
|
879
|
+
'--disposition', 'not-needed', '--evidence', 'Reviewed inline against the agentic-commons review rubric.',
|
|
880
|
+
], ROOT).status, 0, 'an optional binding can be marked not-needed');
|
|
881
|
+
assert.strictEqual(run(controller, [
|
|
882
|
+
'step', '--session-id', 'code-review-not-needed', '--step-id', 'req-review', '--disposition', 'pass',
|
|
883
|
+
'--evidence', 'Review completed without the optional skill.',
|
|
884
|
+
], ROOT).status, 0, 'marking code-review not-needed still lets the review step pass');
|
|
885
|
+
|
|
886
|
+
const unknown = planSession('code-review-unknown-optional', [
|
|
887
|
+
{ id: 'req-review', stepType: 'review', summary: 'Review the change', acceptance: 'findings recorded with evidence tiers', optionalBindings: ['no-such-skill'] },
|
|
888
|
+
{ id: 'req-verify', stepType: 'verification', summary: 'Verify the change', acceptance: 'objective checks pass' },
|
|
889
|
+
]);
|
|
890
|
+
assert.strictEqual(unknown.planned.status, 0, unknown.planned.stderr);
|
|
891
|
+
const undeclared = readJson(unknown.obligationsFile).requirements[0].optionalBindings.find(item => item.id === 'no-such-skill');
|
|
892
|
+
assert.strictEqual(undeclared.availability, 'unknown', 'an unregistered binding id stays unknown');
|
|
893
|
+
assert.strictEqual(run(controller, ['start', '--session-id', 'code-review-unknown-optional'], ROOT).status, 0);
|
|
894
|
+
const cannotLoad = run(controller, [
|
|
895
|
+
'binding', '--session-id', 'code-review-unknown-optional', '--step-id', 'req-review', '--binding-id', 'no-such-skill',
|
|
896
|
+
'--disposition', 'loaded', '--evidence', 'attempted load without a registered path',
|
|
897
|
+
], ROOT);
|
|
898
|
+
assert.strictEqual(cannotLoad.status, 2, cannotLoad.stderr);
|
|
899
|
+
assert.match(cannotLoad.stderr, /no registered load path/, cannotLoad.stderr);
|
|
900
|
+
assert.strictEqual(run(controller, [
|
|
901
|
+
'step', '--session-id', 'code-review-unknown-optional', '--step-id', 'req-review', '--disposition', 'pass',
|
|
902
|
+
'--evidence', 'premature pass while the unknown optional binding is pending',
|
|
903
|
+
], ROOT).status, 2, 'a pending unknown optional binding still blocks a premature step pass');
|
|
904
|
+
assert.strictEqual(run(controller, [
|
|
905
|
+
'binding', '--session-id', 'code-review-unknown-optional', '--step-id', 'req-review', '--binding-id', 'no-such-skill',
|
|
906
|
+
'--disposition', 'not-needed', '--evidence', 'No Harness skill exists under that id.',
|
|
907
|
+
], ROOT).status, 0, 'an unknown optional binding is marked not-needed instead');
|
|
908
|
+
assert.strictEqual(run(controller, [
|
|
909
|
+
'step', '--session-id', 'code-review-unknown-optional', '--step-id', 'req-review', '--disposition', 'pass',
|
|
910
|
+
'--evidence', 'Review completed without any optional skill.',
|
|
911
|
+
], ROOT).status, 0, 'marking the unknown optional binding not-needed lets the step pass');
|
|
912
|
+
|
|
913
|
+
const forbidden = planSession('code-review-review-still-forbids-tdd', [
|
|
914
|
+
{ id: 'req-review', stepType: 'review', summary: 'Review the change', acceptance: 'the change is reviewed', optionalBindings: ['tdd'] },
|
|
915
|
+
{ id: 'req-verify', stepType: 'verification', summary: 'Verify the change', acceptance: 'objective checks pass' },
|
|
916
|
+
]);
|
|
917
|
+
assert.strictEqual(forbidden.planned.status, 2);
|
|
918
|
+
assert.match(forbidden.planned.stderr, /review steps cannot bind tdd/);
|
|
919
|
+
});
|
|
920
|
+
|
|
835
921
|
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
836
922
|
console.log(`\n${failed === 0 ? 'PASS' : 'FAIL'}: Stage A workflow step-type registry (${failed} failures)`);
|
|
837
923
|
process.exit(failed === 0 ? 0 : 1);
|
|
@@ -104,7 +104,7 @@ const packagedSkillNames = fs.readdirSync(path.join(PLUGIN, 'skills'), { withFil
|
|
|
104
104
|
.filter(entry => entry.isDirectory())
|
|
105
105
|
.map(entry => entry.name)
|
|
106
106
|
.sort();
|
|
107
|
-
assert.strictEqual(skillNames.length,
|
|
107
|
+
assert.strictEqual(skillNames.length, 27, 'canonical suite should contain 27 skills');
|
|
108
108
|
assert.deepStrictEqual(packagedSkillNames, skillNames, 'OpenAI package must contain exactly the canonical skill set');
|
|
109
109
|
for (const entry of SOURCE_MANIFEST.skills) {
|
|
110
110
|
const source = path.resolve(ROOT, entry);
|
|
@@ -43,6 +43,22 @@ const releaseRenames = new Map([
|
|
|
43
43
|
['create-agent-launcher', 'multi-agent-workspace']
|
|
44
44
|
]);
|
|
45
45
|
|
|
46
|
+
function logicalSkillName(skill) {
|
|
47
|
+
return releaseRenames.get(skill) || skill;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function compareSkillCatalogs(tagSkills, currentSkills) {
|
|
51
|
+
const releaseLogical = new Set(tagSkills.map(logicalSkillName));
|
|
52
|
+
const currentLogical = new Set(currentSkills.map(logicalSkillName));
|
|
53
|
+
const missing = [...releaseLogical].filter(skill => !currentLogical.has(skill)).sort();
|
|
54
|
+
const additions = [...new Set(currentSkills.filter(skill => !releaseLogical.has(logicalSkillName(skill))))].sort();
|
|
55
|
+
return { missing, additions, exitCode: missing.length ? 1 : 0 };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function formatAdditions(additions) {
|
|
59
|
+
return additions.length ? `Additions since release tag: ${additions.join(', ')}` : '';
|
|
60
|
+
}
|
|
61
|
+
|
|
46
62
|
function git(args) {
|
|
47
63
|
return execFileSync('git', args, { cwd: root, encoding: 'utf8' }).trim();
|
|
48
64
|
}
|
|
@@ -90,15 +106,13 @@ function main() {
|
|
|
90
106
|
process.exit(2);
|
|
91
107
|
}
|
|
92
108
|
const current = new Set(discoverSkills(root));
|
|
93
|
-
const
|
|
94
|
-
const releaseLogical = new Set(tagRoot.map(skill => releaseRenames.get(skill) || skill));
|
|
95
|
-
const missing = [...releaseLogical].filter(skill => !currentLogical.has(skill));
|
|
96
|
-
const extra = [...currentLogical].filter(skill => !releaseLogical.has(skill));
|
|
109
|
+
const comparison = compareSkillCatalogs(tagRoot, [...current]);
|
|
97
110
|
console.log(`Release ${tag}: ${tagRoot.length} skill(s); current tree: ${current.size} skill(s).`);
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
111
|
+
const additionsLine = formatAdditions(comparison.additions);
|
|
112
|
+
if (additionsLine) console.log(additionsLine);
|
|
113
|
+
if (comparison.exitCode !== 0) {
|
|
114
|
+
console.error(`Missing from current tree: ${comparison.missing.join(', ')}`);
|
|
115
|
+
process.exit(comparison.exitCode);
|
|
102
116
|
}
|
|
103
117
|
|
|
104
118
|
let references = 0;
|
|
@@ -140,7 +154,10 @@ function main() {
|
|
|
140
154
|
console.error(`CURRENT RELEASE CHECK FAILED: ${currentReferences.failures.length} dangling reference(s).`);
|
|
141
155
|
process.exit(1);
|
|
142
156
|
}
|
|
143
|
-
|
|
157
|
+
const additionLabel = comparison.additions.length === 1 ? 'addition' : 'additions';
|
|
158
|
+
console.log(`RELEASE SKILL BASELINE PASSED for ${tag}: all ${tagRoot.length} release-tag skills remain present; current source has ${current.size} skill(s), including ${comparison.additions.length} ${additionLabel} not yet in the tag.`);
|
|
144
159
|
}
|
|
145
160
|
|
|
146
161
|
if (require.main === module) main();
|
|
162
|
+
|
|
163
|
+
module.exports = { compareSkillCatalogs, formatAdditions };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const assert = require('assert');
|
|
4
|
+
const { compareSkillCatalogs, formatAdditions } = require('./release-consistency-check');
|
|
5
|
+
|
|
6
|
+
const addedSkill = compareSkillCatalogs(
|
|
7
|
+
['existing-skill'],
|
|
8
|
+
['code-review', 'existing-skill']
|
|
9
|
+
);
|
|
10
|
+
assert.deepStrictEqual(addedSkill.missing, [], 'a release-tag skill must remain present');
|
|
11
|
+
assert.deepStrictEqual(addedSkill.additions, ['code-review']);
|
|
12
|
+
assert.strictEqual(addedSkill.exitCode, 0, 'an extra current skill is informational');
|
|
13
|
+
assert.strictEqual(formatAdditions(addedSkill.additions), 'Additions since release tag: code-review');
|
|
14
|
+
|
|
15
|
+
const missingSkill = compareSkillCatalogs(
|
|
16
|
+
['existing-skill', 'removed-skill'],
|
|
17
|
+
['existing-skill']
|
|
18
|
+
);
|
|
19
|
+
assert.deepStrictEqual(missingSkill.missing, ['removed-skill']);
|
|
20
|
+
assert.strictEqual(missingSkill.exitCode, 1, 'a released skill missing from current source must fail');
|
|
21
|
+
|
|
22
|
+
const renamedSkill = compareSkillCatalogs(
|
|
23
|
+
['build-multi-agent-system'],
|
|
24
|
+
['multi-agent-workspace']
|
|
25
|
+
);
|
|
26
|
+
assert.deepStrictEqual(renamedSkill.missing, [], 'the established rename must satisfy the released entry');
|
|
27
|
+
assert.deepStrictEqual(renamedSkill.additions, [], 'a rename must not be reported as a new addition');
|
|
28
|
+
assert.strictEqual(renamedSkill.exitCode, 0);
|
|
29
|
+
|
|
30
|
+
console.log('Release consistency catalog additions, missing-skill failures, and rename mapping verified.');
|
|
@@ -27,6 +27,12 @@ function check(condition, message) {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
function reviewBindingIds(run) {
|
|
31
|
+
const text = `${run.result.stdout || ''}\n${run.result.stderr || ''}`;
|
|
32
|
+
const line = text.match(/Candidate step binding ids: ([^\r\n]+)/);
|
|
33
|
+
return line ? line[1].split(',').map(id => id.trim()).filter(Boolean) : [];
|
|
34
|
+
}
|
|
35
|
+
|
|
30
36
|
function runTier(prompt, options = {}) {
|
|
31
37
|
const contractPath = path.join(os.tmpdir(), `harness-router-test-${process.pid}-${Math.random().toString(16).slice(2)}.json`);
|
|
32
38
|
const context = options.context || null;
|
|
@@ -91,6 +97,36 @@ if (validContract(iterative, 'iterative-single')) {
|
|
|
91
97
|
check(!plan.requiredInvariants.includes('tdd'), 'binding ids remain step-local rather than workflow invariants');
|
|
92
98
|
}
|
|
93
99
|
|
|
100
|
+
const reviewPositives = [
|
|
101
|
+
'Review PR #412 before we merge it',
|
|
102
|
+
'Review pull request #412',
|
|
103
|
+
'Review this PR before merge',
|
|
104
|
+
'Review the diff',
|
|
105
|
+
'Review the implementation',
|
|
106
|
+
'Re-review the fix at the new HEAD',
|
|
107
|
+
];
|
|
108
|
+
const reviewNegatives = [
|
|
109
|
+
'review project plan',
|
|
110
|
+
'review proposal',
|
|
111
|
+
'review process',
|
|
112
|
+
'preview pr environment',
|
|
113
|
+
'Review this proposal',
|
|
114
|
+
'Review the project plan',
|
|
115
|
+
'Review my process',
|
|
116
|
+
'Re-review the problem statement',
|
|
117
|
+
'Review my implementation plan, ask one question at a time',
|
|
118
|
+
'Preview the diff rendering',
|
|
119
|
+
'Preview PR #412 environment',
|
|
120
|
+
];
|
|
121
|
+
for (const prompt of reviewPositives) {
|
|
122
|
+
const run = runTier(prompt);
|
|
123
|
+
check(reviewBindingIds(run).includes('code-review'), `review signal: ${JSON.stringify(prompt)} yields the code-review candidate`);
|
|
124
|
+
}
|
|
125
|
+
for (const prompt of reviewNegatives) {
|
|
126
|
+
const run = runTier(prompt);
|
|
127
|
+
check(!reviewBindingIds(run).includes('code-review'), `review signal: ${JSON.stringify(prompt)} stays code-review-free`);
|
|
128
|
+
}
|
|
129
|
+
|
|
94
130
|
const memoryPersist = runTier('Persist this lesson as memory after resolving the checkout regression.');
|
|
95
131
|
if (validContract(memoryPersist, 'self-evolve persistence')) {
|
|
96
132
|
const plan = memoryPersist.contract.workflowPlan;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: code-review
|
|
3
|
+
description: "Independently review a PR, diff or finished implementation for correctness, contract and negative-path defects, with source-anchored findings labelled executed, source-traced or inconclusive; read-only by default. USE FOR: \"review this PR before merge\", \"re-review the fix at the new HEAD\". DO NOT USE FOR: \"security threat modeling\", \"implementing or fixing the code\"."
|
|
4
|
+
license: Apache-2.0
|
|
5
|
+
metadata:
|
|
6
|
+
author: Miya Daniel
|
|
7
|
+
version: 0.33.0
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Code Review
|
|
11
|
+
|
|
12
|
+
Independently review completed changes for correctness, contract violations, and negative-path defects. Default to read-only.
|
|
13
|
+
|
|
14
|
+
## 📋 Skill Contract
|
|
15
|
+
|
|
16
|
+
| Component | Specification |
|
|
17
|
+
| :--- | :--- |
|
|
18
|
+
| **Trigger / Input** | Requested review of a PR, diff, implementation, or revised HEAD. |
|
|
19
|
+
| **Expected Output** | Source-anchored findings and an evidence-qualified summary. |
|
|
20
|
+
| **State Mutations** | None unless the user explicitly authorizes an action. |
|
|
21
|
+
| **Enforcement Gate** | Apply the checklist in `<this-skill-dir>/references/review-playbook.md`. |
|
|
22
|
+
|
|
23
|
+
## USE FOR:
|
|
24
|
+
- Review a PR / diff / finished implementation before merge
|
|
25
|
+
- Re-review a revised HEAD: prior-finding disposition + new risks
|
|
26
|
+
|
|
27
|
+
## DO NOT USE FOR:
|
|
28
|
+
- Security-only threat modeling / secrets / OWASP (use `security-review`)
|
|
29
|
+
- Design or plan stress-testing (use `grill-me`); running gates (use `verification-loop`); writing the fix (use `tdd`)
|
|
30
|
+
|
|
31
|
+
## Workflow
|
|
32
|
+
|
|
33
|
+
1. Pin repository, contract, base/head, changed scope, and CI state; inspect relevant callers and consumers.
|
|
34
|
+
2. Independently trace behavior and test applicable counterexamples. Separate severity from safety conditions.
|
|
35
|
+
3. Report only supported findings with evidence tier, smallest correction, regression, and positive control. Use `<this-skill-dir>/templates/finding.template.md`.
|
|
36
|
+
4. On re-review, disposition every prior finding at the new HEAD, then inspect for regressions.
|
|
37
|
+
5. Keep review read-only unless explicitly authorized. Do not claim “bug-free.”
|
|
38
|
+
|
|
39
|
+
In the interim, bind explicitly on an existing review step: `{"stepType":"review","optionalBindings":["code-review"]}`.
|
|
40
|
+
|
|
41
|
+
Deep dive: <this-skill-dir>/references/review-playbook.md
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Review Playbook
|
|
2
|
+
|
|
3
|
+
Use this checklist for the applicable risks; do not force irrelevant categories.
|
|
4
|
+
|
|
5
|
+
## Establish scope and evidence
|
|
6
|
+
|
|
7
|
+
- [ ] Pin repository, PR, base/head SHA, user requirement or PRD/ADR, changed files, relevant callers/callees, and current CI state. Avoid unrelated checkout areas.
|
|
8
|
+
- [ ] Make an independent pass. Treat author explanations and tests as leads, not proof; derive expected behavior from contracts and source.
|
|
9
|
+
- [ ] Label evidence honestly: **executed/reproduced** (command, result, environment, SHA); **source-traced** (concrete path and preconditions); **inconclusive** (missing facts or ambiguous semantics; do not present as a proven blocker).
|
|
10
|
+
- [ ] Inventory user-facing entry points and configuration handoffs (CLI flags, cwd, path spellings, config loading). Exercise them end-to-end, not only the recently edited internals.
|
|
11
|
+
- [ ] Reproduce a candidate finding against the PR base before classifying it as new. Pre-existing defects are follow-ups, not new blockers.
|
|
12
|
+
|
|
13
|
+
## Try applicable counterexamples
|
|
14
|
+
|
|
15
|
+
- [ ] Missing, null, malformed input; old persisted state; base-to-candidate transitions; tenant/scope boundaries.
|
|
16
|
+
- [ ] Missing or broken dependencies: enumerate every entry point, including help, recovery, and stop paths; inspect fallback behavior.
|
|
17
|
+
- [ ] OS/shell differences; concurrency, crash, retry; user paths and symlinks; API/migration compatibility; downstream or generated views.
|
|
18
|
+
- [ ] Follow each important diagnostic/fact through every consumer and ancestor. Check whether required facts are computed before derived authority, indexes, or output.
|
|
19
|
+
- [ ] Compare new rules with previously accepted rules and controls. Separate display severity from safety prerequisites.
|
|
20
|
+
|
|
21
|
+
## Check tests and output
|
|
22
|
+
|
|
23
|
+
- [ ] Ask whether each test still passes if the behavior is disabled, bypassed, or broken (mutation thought experiment). Confirm the test is included in CI.
|
|
24
|
+
- [ ] When output text changes, compare the intended changed lines with literal templates; do not exclude changed lines from assertions.
|
|
25
|
+
- [ ] Dedupe findings. Use P1 for severe merge blockers, P2 for meaningful correctness/contract defects, P3 for low-risk issues; avoid speculation.
|
|
26
|
+
- [ ] For each finding, include exact path:line or diff anchor at HEAD SHA, trigger, observed behavior, violated contract and impact, evidence tier, smallest fix, negative regression, positive control, and disposition.
|
|
27
|
+
|
|
28
|
+
## Re-review and conclude
|
|
29
|
+
|
|
30
|
+
- [ ] Pin the new HEAD. Disposition every prior finding separately: resolved, still open, superseded, or inconclusive. Then inspect fix-induced side effects and new changes.
|
|
31
|
+
- [ ] Stay read-only unless the user/workflow explicitly authorizes action. Do not edit, comment, label, or merge on review authority alone.
|
|
32
|
+
- [ ] Summarize scope/SHA, CI, covered and unexamined risks, fixed/open/new counts, and evidence-grounded merge recommendation. “No actionable findings in examined scope” does not mean “bug-free.” Distinguish repository tests from live-host evidence.
|
|
33
|
+
|
|
34
|
+
## Per-finding format
|
|
35
|
+
|
|
36
|
+
`[P1/P2/P3] Title` — `path:line @ <HEAD SHA>`
|
|
37
|
+
|
|
38
|
+
**Trigger** → **behavior** → **contract violation / impact**. **Evidence:** executed / source-traced / inconclusive. **Minimal correction:** … **Regression:** negative case …; positive control … **Disposition:** blocker / follow-up / owner decision.
|
|
39
|
+
|
|
40
|
+
Use `<this-skill-dir>/templates/finding.template.md` for the complete findings and summary form.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Code Review — <PR or change>
|
|
2
|
+
|
|
3
|
+
## Scope and evidence
|
|
4
|
+
|
|
5
|
+
- Repository / PR:
|
|
6
|
+
- Base SHA → reviewed HEAD SHA:
|
|
7
|
+
- Requirement / PRD / ADR:
|
|
8
|
+
- Changed files and relevant callers/consumers:
|
|
9
|
+
- CI state and commands observed:
|
|
10
|
+
- Counterexample categories covered:
|
|
11
|
+
- Risks not examined / evidence limits:
|
|
12
|
+
|
|
13
|
+
## Findings
|
|
14
|
+
|
|
15
|
+
### [P1/P2/P3] <Short, specific title>
|
|
16
|
+
|
|
17
|
+
- **Location:** `<path>:<line or diff anchor> @ <HEAD SHA>`
|
|
18
|
+
- **Trigger:** <minimal input, state transition, or interleaving>
|
|
19
|
+
- **Behavior → impact:** <observed or source-traced behavior and violated contract>
|
|
20
|
+
- **Evidence tier:** <executed/reproduced | source-traced | inconclusive; include exact command/result or preconditions>
|
|
21
|
+
- **Minimal correction:** <smallest feasible fix>
|
|
22
|
+
- **Regression:** Negative case: <case that fails before fix>; positive control: <valid behavior that remains valid>
|
|
23
|
+
- **Disposition:** <blocker | follow-up | owner decision; for re-review, resolved | still open | superseded | inconclusive>
|
|
24
|
+
|
|
25
|
+
<!-- Duplicate this finding section as needed. Remove headings for which there are no findings. -->
|
|
26
|
+
|
|
27
|
+
## Summary
|
|
28
|
+
|
|
29
|
+
- **Scope / reviewed SHA:**
|
|
30
|
+
- **CI evidence:**
|
|
31
|
+
- **Covered / unexamined risks:**
|
|
32
|
+
- **Findings:** <fixed / open / new counts on re-review>
|
|
33
|
+
- **Conclusion:** <merge recommendation grounded in evidence; “no actionable findings in examined scope” is not a claim of bug-free code>
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Document Lifecycle Contract (#325)
|
|
2
|
+
|
|
3
|
+
Contract for the documentation family: project docs configuration (D5), document roles and revision metadata (D6.1–D6.3), domain-scoped IDs (D6.8) and the generated index. Owner decisions live in [#325](https://github.com/dyphn1/Harness-everything/issues/325); this file is the implementation contract that tests cite.
|
|
4
|
+
|
|
5
|
+
Documentation is a **consumer** of the #320 workflow/obligation abstractions. Nothing here is a second router, a standing maintenance loop or a cognitive lock: every check is a pure function that reports diagnostics.
|
|
6
|
+
|
|
7
|
+
## Scope of this slice
|
|
8
|
+
|
|
9
|
+
| Phase | Status here |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| 0 — contract + RED fixtures | This file; fixtures under `ci/fixtures/doc-lifecycle/` |
|
|
12
|
+
| 1a — `project-docs.json` loader | `multi-agent-workspace/scripts/project-docs-resolver.js` |
|
|
13
|
+
| 2 — roles, IDs, aliases, legacy, supersession, index, D6.9 PRD revisions | `harness-everything/scripts/doc-lifecycle.js` |
|
|
14
|
+
| 1b, 3 (`doc-change`, `requirement-sync`) | Not started: blocked by #320 family metadata, D1 obligation schema and D3 evidence states |
|
|
15
|
+
| 4 — derived-view generators, `describes:` drift | Not started |
|
|
16
|
+
|
|
17
|
+
## Configuration (D5)
|
|
18
|
+
|
|
19
|
+
| Source | Rule |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| `.agents/harness-everything/project-docs.json` | Committed, host-neutral. When present it **wholly overrides** host-manifest `projectDocs` (no field merge); warn `project-overrides-host` when a host value differs |
|
|
22
|
+
| Invalid project file | Status `invalid` with diagnostics; no fallback to host manifests; config-dependent MUSTs report unresolved |
|
|
23
|
+
| No project file, active host known and declares `projectDocs` | Status `host`; record that manifest as the source |
|
|
24
|
+
| No project file, active host known but its manifest has no `projectDocs` | **Owner decision 2026-10-17:** `none`, recording the active host; never inherit another host's config. Warn and suggest `project-docs.json` |
|
|
25
|
+
| No project file, host unknown | All declaring hosts canonically equal → `host` (all sources recorded); they differ → `ambiguous`; none → `none` |
|
|
26
|
+
| AGENTS.md | Never a configuration source |
|
|
27
|
+
|
|
28
|
+
- Canonical comparison: object keys sorted recursively, array order preserved, whitespace irrelevant.
|
|
29
|
+
- The project file may also carry the existing zone fields (`decisionLocation`, `domainLocation`, `architectureLocation` and their aliases). Zone resolution uses the project file when present, so the override is total.
|
|
30
|
+
- Host manifests that fail to parse are skipped with an `invalid-host-config` diagnostic naming the source; they never count as agreeing or disagreeing.
|
|
31
|
+
- **Generator security (MUST):** reading or validating config never executes `derived[].generator`. Execution belongs to an authorized verification step only.
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"governed": [
|
|
36
|
+
{ "path": "docs/prd/**", "role": "prd" },
|
|
37
|
+
{ "path": "docs/adr/**", "role": "adr" }
|
|
38
|
+
],
|
|
39
|
+
"domains": ["GRPH", "IFCE"],
|
|
40
|
+
"legacy": ["docs/adr/legacy/**"],
|
|
41
|
+
"derived": [{ "path": "docs/roadmap/index.md", "generator": "npm run docs:index" }],
|
|
42
|
+
"tickets": "tracker"
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
| Field | Validation |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| `governed` | Array of `{ path: glob, role }`; `role` from the fixed vocabulary below |
|
|
49
|
+
| `domains` | Array of unique `^[A-Z][A-Z0-9]{1,9}$`. Reserved for ID prefixes: a legacy string value in a host manifest is read as `domainLocation` with a `deprecated-domains-string` warning; in `project-docs.json` it is a schema error |
|
|
50
|
+
| `legacy` | Array of globs |
|
|
51
|
+
| `derived` | Array of `{ path, generator }`, both non-empty strings |
|
|
52
|
+
| `tickets` | `"tracker"` or a repo-relative path |
|
|
53
|
+
| Unknown keys | Warning, not error |
|
|
54
|
+
|
|
55
|
+
Globs are repo-relative POSIX paths supporting `*` (one segment) and `**` (any depth).
|
|
56
|
+
|
|
57
|
+
## Roles and status (D6.1, D6.3)
|
|
58
|
+
|
|
59
|
+
| Role | Status vocabulary | `id` |
|
|
60
|
+
| --- | --- | --- |
|
|
61
|
+
| `prd` | `draft` / `approved` / `effective` / `rejected` / `withdrawn` (D6.9); pre-migration `active` / `superseded` warn unchanged and error when added or changed | Required: `PRD-<DOMAIN>-NNN` plus `revision` (D6.9) |
|
|
62
|
+
| `adr` | `proposed` / `accepted` / `rejected` / `superseded` | Required: `<DOMAIN>-NNN` |
|
|
63
|
+
| `ticket` | `open` / `blocked` / `done` / `withdrawn` | Optional |
|
|
64
|
+
| `reference` | `active` / `deprecated` | Optional |
|
|
65
|
+
| `log`, `report` | `final` / `archived` | Optional |
|
|
66
|
+
|
|
67
|
+
- Role comes from the first matching `governed` entry; a frontmatter `role` must agree with it.
|
|
68
|
+
- **Legacy immutability:** Legacy files remain outside the active ID namespace. With an observed trusted base, a path is frozen only when some base source's `legacy` matches it and no base source positively governs it after that source's own legacy exclusion; parsed frontmatter stays deep-equal while body/editorial/link edits remain allowed. A surviving path is reclassified when it is no longer legacy under the same per-source rule for candidate sources and emits `legacy-frozen-modified`; deletion/move relies on existing escalation for surviving explicit `legacy:ID@path` references. Without a trusted base, legacy immutability is not-observed or unavailable.
|
|
69
|
+
- Frontmatter fields: `id`, `role`, `status`, `supersedes`, `aliases`. `superseded_by` and `amends` are not stored; `superseded_by` is derived in the index.
|
|
70
|
+
- **Ratchet:** schema checks (role/status/ID) apply to governed files named in `changedPaths`. Full mode is explicit (`--all` / `{ all: true }`); the CLI refuses to run without `--changed`, `--changed-path` or `--all`. Cross-file integrity diagnostics are errors when any involved file is changed (deleted and renamed paths count as changed), warnings otherwise. Warnings that affect resolution still appear in the index's unresolved list. CLI `--changed` entries resolve against `--workspace`, normalize to repo-relative POSIX paths, and fail as usage errors when outside the workspace; `--changed` takes one comma-separated argument and cannot represent filenames containing commas, so repeat `--changed-path <path>` (one occurrence per path, never comma-split, normalizes and resolves like the CSV form, and mutually exclusive with `--all`; may be combined with `--changed`) when a filename contains a comma. Occurrences of either changed selector accumulate: repeated `--changed` or `--changed-path` flags add to the changed set instead of overwriting earlier declarations. The CSV form trims whitespace around its comma-separated items; `--changed-path` preserves commas and outer whitespace while resolving paths and normalizing separators. Both `/` and `\` are separators on every host; literal backslashes in POSIX filenames cannot be selected through these CLI flags — use `--all` or the API `changedPaths` for those files. Relative `--config` paths also resolve from `--workspace`. Without a trusted base, escalation for deleted or renamed targets covers explicit `legacy:ID@path` selectors only; ID-only references escalate when a trusted base (`--base` or a supplied inventory, see D6.9) maps the ID to a changed path.
|
|
71
|
+
- Parsing conventions: role comes from the first matching `governed` entry; frontmatter is the leading `---` block in a YAML subset (scalars, inline `[a, b]` lists, `- a` block lists); references inside fenced code blocks are ignored.
|
|
72
|
+
|
|
73
|
+
## IDs, aliases and references (D6.2, D6.8)
|
|
74
|
+
|
|
75
|
+
- ADR: `<DOMAIN>-NNN` (three or more digits). REQ: `REQ-<DOMAIN>-NNN`, defined only in a PRD as a heading `REQ-<DOMAIN>-NNN` at the start of a Markdown heading line (`### REQ-GRPH-012: …`).
|
|
76
|
+
- **REQ aliases — owner decision 2026-10-17:** declared on the REQ heading itself, `### REQ-GRAPH-012 (aliases: REQ-GRPH-012, REQ-GRPH-099): title`. Each alias resolves directly to that REQ; collision rules match document aliases. Document-level `aliases` never rename REQs.
|
|
77
|
+
- `DOMAIN` must be declared in `domains`. Aliases keep their historical domain, which need not be declared.
|
|
78
|
+
- `aliases: [...]` lists every previous ID; each alias resolves **directly** to the declaring document. Reject duplicate canonical IDs, an alias equal to another active canonical ID or alias, and unresolved references.
|
|
79
|
+
|
|
80
|
+
| Reference | Resolution |
|
|
81
|
+
| --- | --- |
|
|
82
|
+
| `GRPH-007` | Active canonical IDs and aliases only; never falls through to legacy |
|
|
83
|
+
| `legacy:GRPH-007` | The unique legacy document with that ID. Several matches → `ambiguous-legacy-ref` listing every candidate path; never the first match |
|
|
84
|
+
| `legacy:GRPH-007@docs/adr/legacy/v2/GRPH-007.md` | **Owner decision 2026-10-17:** the repo-relative path is the archive discriminator; resolves only when that file is legacy and carries that ID |
|
|
85
|
+
|
|
86
|
+
Selector grammar: `legacy:` + ID, optionally `@` + path, split at the **first** `@`. The path is a repo-relative POSIX path compared byte-exactly with the collected path (case-sensitive on every OS). Backslashes, leading `/`, drive letters, `.` or `..` segments, and paths outside the workspace are `invalid-ref`; they are never normalized into a match. A renamed archive path leaves old selectors `unresolved-ref` until updated.
|
|
87
|
+
|
|
88
|
+
- A legacy document's ID is its frontmatter `id`, else its file stem when that matches an ID pattern. Duplicate legacy IDs are allowed; only bare references to them are ambiguous.
|
|
89
|
+
- An active alias equal to a legacy ID is allowed (the rename case).
|
|
90
|
+
- Aliases are strings matching an ADR or REQ ID pattern. The `legacy:` prefix is reserved for references and is `invalid-alias` inside `aliases`, as are self-aliases and non-strings.
|
|
91
|
+
|
|
92
|
+
## Supersession (D6.3, D6.4)
|
|
93
|
+
|
|
94
|
+
- `supersedes` is written on the new document and means real normative replacement, never an identity rename.
|
|
95
|
+
- Active target: the target must carry `status: superseded`, else `supersede-status-mismatch`. This is the only required two-file edit.
|
|
96
|
+
- **Legacy target — owner decision 2026-10-17 (external projection):** reference it with an explicit `legacy:` ref that resolves uniquely. The legacy file is never edited; the generated index projects its status as superseded by the active document. This is the explicit legacy exception to both the D6.3 two-file lint and the D6.6 "ADR superseded → old status `superseded`" event: for legacy targets the obligation is met by the projection. The index shows the stored status and the projected effective status side by side, with the source edge.
|
|
97
|
+
- A plain ref that matches only a legacy ID is `unresolved-ref` with a hint to use `legacy:`.
|
|
98
|
+
- Self-supersession and supersession cycles are errors.
|
|
99
|
+
- **Branching and eligibility — owner decision 2026-10-17:** a target may have several successors; the index lists them sorted. Only edges from an `accepted` ADR or an `active` PRD are effective: they require the active target's `superseded` status and drive legacy projection. Edges from other statuses (`draft`, `proposed`, …) are shown as pending, do not require the target's status change, and do not project.
|
|
100
|
+
- Only explicit `supersedes` or a recorded owner decision resolves a conflict.
|
|
101
|
+
- **Recorded conflicts — owner decision 2026-10-17:** semantic PRD/ADR contradictions are not detected from prose. Whoever finds one records `conflicts_with: [<ID>]` on either document; the pair stays in the unresolved list (`recorded-conflict`, both IDs and paths) until an effective `supersedes` between them exists. Owner adjudication is recorded by that `supersedes` edge or by removing the entry in the same change that updates the canonical document. Structural conflicts are detected automatically.
|
|
102
|
+
|
|
103
|
+
## Diagnostics
|
|
104
|
+
|
|
105
|
+
Each diagnostic is `{ code, severity, path, id?, message, candidates? }`, sorted by `path`, `code`, `id`, then `message`.
|
|
106
|
+
|
|
107
|
+
Anchors for cross-file diagnostics: `supersede-*`, `unresolved-ref`, `ambiguous-legacy-ref`, `invalid-ref` and `recorded-conflict` attach to the document that declares the reference or edge. `legacy-frozen-modified` attaches to the trusted-base archive path. `duplicate-id`, `alias-collision` and `duplicate-req` emit one diagnostic on **every** involved document, each listing the others in `candidates`.
|
|
108
|
+
|
|
109
|
+
| Code | Trigger |
|
|
110
|
+
| --- | --- |
|
|
111
|
+
| `malformed-frontmatter` | Unparseable frontmatter block |
|
|
112
|
+
| `role-mismatch` | Frontmatter `role` differs from path role |
|
|
113
|
+
| `missing-status` / `invalid-status` | Status absent or outside the role vocabulary |
|
|
114
|
+
| `missing-id` / `invalid-id` / `unknown-domain` | ADR ID absent, malformed, or domain undeclared |
|
|
115
|
+
| `duplicate-id` | Two active documents share a canonical ID |
|
|
116
|
+
| `alias-collision` | Alias equals another active canonical ID or alias |
|
|
117
|
+
| `duplicate-req` / `missing-req` | REQ defined twice in active PRDs, or referenced in a governed doc but never defined |
|
|
118
|
+
| `unresolved-ref` / `ambiguous-legacy-ref` / `invalid-ref` | Reference resolves to nothing, to several legacy documents, or is malformed |
|
|
119
|
+
| `legacy-frozen-modified` | With a trusted base, legacy frontmatter changed or a surviving archive was reclassified out of legacy scope (error) |
|
|
120
|
+
| `invalid-alias` | Alias is not a string ID, is self-referential, or uses the reserved `legacy:` prefix |
|
|
121
|
+
| `invalid-host-config` | A host manifest's `projectDocs` failed to parse or validate |
|
|
122
|
+
| `supersede-status-mismatch` / `supersede-cycle` | Effective edge to an active target that is not `superseded`; self or cyclic supersession |
|
|
123
|
+
| `recorded-conflict` | `conflicts_with` pair without an effective `supersedes` between them |
|
|
124
|
+
|
|
125
|
+
## Contract-style PRD revisions (D6.9)
|
|
126
|
+
|
|
127
|
+
**Owner direction 2026-10-17:** an effective PRD is a contract. A change creates a new **complete** revision copied from the current effective revision; the effective snapshot is never edited in place. This replaces the D6.1 "living PRD" model for PRDs only; ADRs, tickets and other roles are unchanged. Where this section conflicts with earlier PRD rules (optional PRD `id`, PRD status vocabulary, PRD `supersedes`), this section wins.
|
|
128
|
+
|
|
129
|
+
### Identity and graph
|
|
130
|
+
|
|
131
|
+
| Concept | Rule |
|
|
132
|
+
| --- | --- |
|
|
133
|
+
| Logical PRD | `id: PRD-<DOMAIN>-NNN`, shared by every revision; grouped by canonical identity (aliases preserved) |
|
|
134
|
+
| Revision | `revision: <N>` positive integer, no leading zeros; canonical form `PRD-GRPH-001@r3` |
|
|
135
|
+
| Parent | `previous_revision: <N>`; must name an existing revision of the same logical PRD with a smaller number. Exactly one baseline (no parent) per logical PRD |
|
|
136
|
+
| Layout | Not mandated (recommended `docs/prd/<slug>/r003.md`); identity comes from frontmatter only |
|
|
137
|
+
| Uniqueness | `(logical id, revision)` unique. Repeated logical IDs and inherited aliases across revisions are not `duplicate-id` / `alias-collision` |
|
|
138
|
+
|
|
139
|
+
### Status, approval, activation
|
|
140
|
+
|
|
141
|
+
| Stored status | Editable | Notes |
|
|
142
|
+
| --- | --- | --- |
|
|
143
|
+
| `draft` | Yes | Never authoritative; drafts may fork |
|
|
144
|
+
| `approved` | Activation fields only | Approval bound to content |
|
|
145
|
+
| `effective` | **No**, from the change that sets it | Frozen artifact |
|
|
146
|
+
| `rejected` / `withdrawn` | Not enforced in this slice | Never selectable as current |
|
|
147
|
+
|
|
148
|
+
- **Approval record (owner decision 2026-10-17):** `approval: { by, at, ref, content_digest }`. `by` is a human or an independent cold verifier, never the drafting agent. `ref` is a URL or an in-repo record path (offline-checkable). `content_digest` covers the revision body plus non-activation frontmatter; a content edit after approval invalidates it (`approval-content-mismatch`).
|
|
149
|
+
- **Activation:** sets `status: effective` and `effective_at`; only these fields may change between `approved` and `effective`. The file path must also stay the same as in the trusted approved baseline. An `approved` revision is frozen except for activation; any other edit, including rebinding `approval`, requires returning it to `draft` and re-approving (`invalid-transition`). Deleting an approved revision or moving it outside the candidate inventory is also an `invalid-transition`. Timestamps are ISO 8601 with timezone; `effective_at` must not precede `approval.at`. Activation requires the parent to be the current effective revision (`stale-draft-base`).
|
|
150
|
+
- **Current effective** is derived after validating the whole graph: a connected chain of `effective` revisions from the baseline; two effective children of one parent → `revision-fork`; no effective revision → explicit pre-activation state. Older effective revisions are derived `historical`; their files are never edited.
|
|
151
|
+
|
|
152
|
+
### Digests and evidence envelope
|
|
153
|
+
|
|
154
|
+
- **Stored bytes:** the bytes Git stores for the file (after `git add` end-of-line normalization). Base content comes from the trusted base tree; candidate content is normalized the same way. Comparisons may use Git blob object IDs; frontmatter digests are `sha256:<hex>` over stored bytes. Pure API callers pass stored bytes directly.
|
|
155
|
+
- **Artifact digest** (`previous_digest`, immutability): sha256 over the complete stored bytes of the revision file.
|
|
156
|
+
- **Approval content digest** (`approval.content_digest`): sha256 over `JSON(frontmatter without status, effective_at, approval; keys sorted; UTF-8) + "\n" + body` (body in stored bytes). Excluding `approval` avoids self-reference; excluding activation fields lets activation keep the digest valid.
|
|
157
|
+
- **Independence:** `drafted_by` (required on `approved` and `effective` revisions; missing → `approval-missing`) names the drafting author; `approval.by` equal to `drafted_by` → `approval-not-independent`. `approval.role` is `human` or `cold-verifier`. The checker verifies the record's presence, binding and independence, not that a review actually happened.
|
|
158
|
+
- **Evidence envelope:** `analyzeDocs(docs, config, opts)` returns `{ diagnostics, evidence }` with `evidence.immutability` = `observed` | `not-observed` | `unavailable`. `lintDocs` stays the diagnostics-only projection. CLI JSON prints the envelope; exit 1 only for error diagnostics, and `not-observed` is always printed, never folded into success.
|
|
159
|
+
- **Offline base inventory:** `analyzeDocs(docs, config, { baseInventory: [{ id, revision, path, status, bytes }] })` supplies stored baseline bytes for offline checks. For Git-equivalent legacy freezing, supply either per-entry boolean `legacy` classifications computed under the per-source rule or `baseConfigSources`; when both are supplied, `baseConfigSources` takes precedence and applies the source rule. The supplied historical sources also govern migration checks, REQ identity lookup, and changed-scope calculations. Without either, legacy classification is not established for unflagged rows, which are not frozen, and `evidence.legacyImmutability` reports `not-observed` while byte-based `evidence.immutability` remains `observed`. Candidate classification uses `options.configSources` when supplied, otherwise `config`; equivalent trusted bytes and authority inputs must yield the same diagnostics as `--base`.
|
|
160
|
+
- Tests isolate Git (no global/system config, hooks, templates or signing; explicit `core.autocrlf` and attributes) and compute expected digests independently of the system under test.
|
|
161
|
+
|
|
162
|
+
### Immutability evidence
|
|
163
|
+
|
|
164
|
+
- **Byte basis (owner decision 2026-10-17):** digests use the repository-stored content (Git blob bytes, or an exported snapshot of them), so checkout line-ending conversion cannot change identity.
|
|
165
|
+
- `previous_digest` in each revision is an **internal consistency** check only; editing a parent and its child's digest together stays self-consistent.
|
|
166
|
+
- **Immutability** needs a trusted baseline: CLI `--base <git-rev>` or a supplied snapshot inventory. Base and candidate inventories are compared by `(logical id, revision)` and by path: a base-effective revision must remain present with identical bytes regardless of status edits, moves, deletions or config exclusions (`immutable-revision-modified`, `immutable-revision-deleted`). Without a trusted baseline the result is `not-observed`, never pass. The immutability scope is the **union** of governed, legacy and PRD globs from the current config and from every config source in the base tree (`project-docs.json`, each host manifest's `projectDocs`, and the base copy of an explicit `--config` path); narrowing the current config never removes a base-effective revision from checking. Stored-byte normalization follows Git's text/binary decision (including NUL detection for `text=auto` and for `core.autocrlf` without attributes); content Git stores unchanged is compared unchanged. Git is read with argument arrays, never shell interpolation; missing history is never fetched.
|
|
167
|
+
|
|
168
|
+
### REQ continuity
|
|
169
|
+
|
|
170
|
+
- Every REQ of the parent appears in the child unless accounted for by `removed_reqs: [...]` or a `replaces` edge; otherwise `req-dropped`. Each REQ block needs non-empty acceptance content (`incomplete-snapshot`). ID continuity is structural evidence only; semantic completeness still needs review.
|
|
171
|
+
- Unchanged and editorial REQs keep their IDs. A REQ whose text changed is reported as changed and needs a review classification (normative vs editorial); IDs are never recycled for unrelated meaning.
|
|
172
|
+
- Heading clauses: `### REQ-GRPH-020 (replaces: REQ-GRPH-012, REQ-GRPH-013; aliases: REQ-GRAPH-020): title`. Clauses are `;`-separated, comma lists, each at most once; unknown or duplicate clauses are errors. `replaces` allows splits and merges; a REQ ID may not be both alias and replaced target; replacement cycles are errors.
|
|
173
|
+
- `duplicate-req` applies within each revision and across the current effective revisions of different logical PRDs.
|
|
174
|
+
- Plain `REQ-…` resolves only in current effective revisions; a removed or replaced REQ is `unresolved-ref` with a historical/replacement hint, never auto-followed.
|
|
175
|
+
|
|
176
|
+
### References
|
|
177
|
+
|
|
178
|
+
| Ref | Resolves to |
|
|
179
|
+
| --- | --- |
|
|
180
|
+
| `PRD-GRPH-001` | Current effective revision |
|
|
181
|
+
| `PRD-GRPH-001@r2` | Exactly revision 2, returned with status, digest and provenance |
|
|
182
|
+
| `PRD-GRPH-001@r2#REQ-GRPH-012` | That REQ inside revision 2 (aliases resolved as of r2) |
|
|
183
|
+
|
|
184
|
+
- Parse the namespace first: `legacy:` refs keep their exact path grammar; `@r<N>` and `#REQ` apply only to PRD logical IDs. Whole-token anchored parsing; a valid prefix of an invalid ref never resolves.
|
|
185
|
+
- Pins to `draft`/`approved` revisions are provisional and never satisfy governed-implementation evidence; audit evidence pins an effective revision plus its digest.
|
|
186
|
+
|
|
187
|
+
### Cross-PRD replacement and migration
|
|
188
|
+
|
|
189
|
+
- **Retirement eligibility:** only an `effective` source revision retires a target or resolves a recorded conflict; edges from `draft`/`approved`/`rejected` revisions are pending (same rule as ADR effective edges).
|
|
190
|
+
- **Retirement is a historical event (owner decision 2026-10-17):** once any revision that was effective declares the edge, the target stays retired even if later revisions of the source omit it; omission never revives a target. The index records the revision that issued the retirement.
|
|
191
|
+
- **Retirement (owner decision 2026-10-17):** `supersedes: [PRD-GRPH-001@r4]` on another logical PRD retires that contract by external projection, like legacy targets: no file is edited, splits and merges are allowed, the exact source/target revisions are recorded, and the retired PRD stays addressable.
|
|
192
|
+
- **Baseline adoption (owner decision 2026-10-17):** an existing non-legacy living PRD gets one reviewed metadata-only edit adding `revision: 1`, approval and a `migrated_from_digest` of its prior content, recorded in a migration record. The baseline frontmatter names the record with `migration_record: <repo-relative path>`; the record is JSON `{ record: "prd-baseline-migration", id, revision, source, migrated_from_digest, approved_by }` and must match the frontmatter and the base content of `source` (`migration-record-missing` / `migration-record-mismatch`). `baseline` status never exempts later edits. Legacy PRDs stay frozen outside the graph.
|
|
193
|
+
|
|
194
|
+
### Diagnostics (D6.9)
|
|
195
|
+
|
|
196
|
+
`invalid-prd-id`, `invalid-revision`, `duplicate-revision`, `missing-parent`, `multiple-baselines`, `revision-cycle`, `revision-fork`, `stale-draft-base`, `invalid-transition`, `approval-missing`, `approval-content-mismatch`, `revision-digest-mismatch`, `immutable-revision-modified`, `immutable-revision-deleted`, `req-dropped`, `incomplete-snapshot`, `invalid-req-clause`, `replacement-alias-overlap`, `replacement-cycle`. `not-observed` is an evidence disposition, not a diagnostic and not a pass.
|
|
197
|
+
|
|
198
|
+
## Packaging
|
|
199
|
+
|
|
200
|
+
Source checkout, Codex plugin and Claude plugin copy whole skill trees. Callers locate `doc-lifecycle.js` and the resolver relative to their own skill directory, never the source-tree root. A to-spec-only install must use the same resolver implementation (copied byte-identically and checked), and a broken shared module is an error, never a silent fallback to older host scanning.
|
|
201
|
+
|
|
202
|
+
## Generated index
|
|
203
|
+
|
|
204
|
+
`buildIndex` returns Markdown with no timestamps or environment data, so two runs over the same tree are byte-identical. Sections: active documents (sorted by ID then path, with derived `superseded by`), legacy documents (sorted by ID then path, with projected status), and the unresolved list (resolution-affecting error diagnostics).
|
|
205
|
+
|
|
206
|
+
The shared `AUTHORITY_BLOCKER_DIAGNOSTICS` taxonomy covers `invalid-prd-id`, `invalid-revision`, `invalid-status`, `invalid-alias`, `duplicate-revision`, `missing-parent`, `multiple-baselines`, `revision-cycle`, `revision-fork`, `revision-digest-mismatch`, `req-dropped`, `incomplete-snapshot`, `invalid-req-clause`, `duplicate-req`, `replacement-alias-overlap`, `replacement-cycle`, `approval-missing`, `approval-not-independent`, `approval-content-mismatch`, `invalid-transition`, `stale-draft-base`, `migration-record-missing`, `migration-record-mismatch`, `immutable-revision-modified`, `immutable-revision-deleted`, `legacy-frozen-modified`, `duplicate-id`, `alias-collision`, `unknown-domain`, `invalid-id` and `role-mismatch`. Retirement eligibility uses the validated effective prefix and blocks on these authority findings regardless of ratchet severity; this prevents a known identity, alias or REQ collision from authorizing a new retirement. Namespace and REQ collisions are computed before the final prefix and supersession/conflict projection. Index current-state display uses the same blocker taxonomy with error severity: an error on the graph-selected current revision or a required effective ancestor produces `state: invalid; current: none`, while warnings stay visible in Unresolved without revoking `valid/current`. A pending descendant cannot discard an earlier valid effective prefix or its historical retirement event. The unresolved list also includes resolution diagnostics `malformed-frontmatter`, `missing-id`, `missing-req`, `unresolved-ref`, `ambiguous-legacy-ref`, `invalid-ref`, `missing-status`, `supersede-status-mismatch`, `supersede-cycle`, `recorded-conflict` and `prd-migration-required`; these remain visible at their reported severity but do not themselves block retirement eligibility or current-state display. When `buildIndex` receives no `diagnostics`, it runs `analyzeDocs` with the supplied options (including trusted-base inputs) and renders that result; callers that provide precomputed diagnostics must provide diagnostics from the same analysis context, with its graph and validated-prefix set, for equivalent retirement and current-state output.
|
package/docs/openai-plugin.md
CHANGED
|
@@ -74,7 +74,7 @@ The local package path is distinct from the general `--codex` installer. The gen
|
|
|
74
74
|
The package tests prove the following repository-side contract:
|
|
75
75
|
|
|
76
76
|
- the portable root manifest and `.codex-plugin` compatibility manifest agree on identity, version, interface, and hook entry point;
|
|
77
|
-
- the root `skills/` tree contains exactly the
|
|
77
|
+
- the root `skills/` tree contains exactly the 27 canonical skills;
|
|
78
78
|
- Windows and POSIX hook commands resolve through `PLUGIN_ROOT`;
|
|
79
79
|
- session start emits the compact policy and prompt submission invokes invariant-first routing;
|
|
80
80
|
- supported local `Bash` and `apply_patch` calls receive the packaged guards and state tracking;
|