harness-everything 0.40.0 → 0.41.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/.agents/harness-everything/project-docs.json +9 -0
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/.claude-plugin/plugin.json +1 -1
  4. package/CHANGELOG.md +51 -0
  5. package/benchmarks/results/f1-doc-path-baseline/1-source-clean.stderr.txt +0 -0
  6. package/benchmarks/results/f1-doc-path-baseline/1-source-clean.stdout.json +20 -0
  7. package/benchmarks/results/f1-doc-path-baseline/2-injected-architecture.md +229 -0
  8. package/benchmarks/results/f1-doc-path-baseline/2-source-injected.stderr.txt +0 -0
  9. package/benchmarks/results/f1-doc-path-baseline/2-source-injected.stdout.json +33 -0
  10. package/benchmarks/results/f1-doc-path-baseline/3-source-reverted.stderr.txt +0 -0
  11. package/benchmarks/results/f1-doc-path-baseline/3-source-reverted.stdout.json +20 -0
  12. package/benchmarks/results/f1-doc-path-baseline/4a-packaged-clean.stderr.txt +0 -0
  13. package/benchmarks/results/f1-doc-path-baseline/4a-packaged-clean.stdout.json +20 -0
  14. package/benchmarks/results/f1-doc-path-baseline/4b-packaged-injected.stderr.txt +0 -0
  15. package/benchmarks/results/f1-doc-path-baseline/4b-packaged-injected.stdout.json +33 -0
  16. package/benchmarks/results/f1-doc-path-baseline/5-source-missing-config.stderr.txt +0 -0
  17. package/benchmarks/results/f1-doc-path-baseline/5-source-missing-config.stdout.json +15 -0
  18. package/benchmarks/results/f1-doc-path-baseline/5b-packaged-missing-config.stderr.txt +0 -0
  19. package/benchmarks/results/f1-doc-path-baseline/5b-packaged-missing-config.stdout.json +15 -0
  20. package/benchmarks/results/f1-doc-path-baseline/README.md +54 -0
  21. package/benchmarks/results/f1-doc-path-baseline/before-fix/1-source-clean.stderr.txt +0 -0
  22. package/benchmarks/results/f1-doc-path-baseline/before-fix/1-source-clean.stdout.json +115 -0
  23. package/benchmarks/results/f1-doc-path-baseline/before-fix/2-injected-document.md +328 -0
  24. package/benchmarks/results/f1-doc-path-baseline/before-fix/2-source-injected.stderr.txt +0 -0
  25. package/benchmarks/results/f1-doc-path-baseline/before-fix/2-source-injected.stdout.json +127 -0
  26. package/benchmarks/results/f1-doc-path-baseline/before-fix/3-source-reverted.stderr.txt +0 -0
  27. package/benchmarks/results/f1-doc-path-baseline/before-fix/3-source-reverted.stdout.json +115 -0
  28. package/benchmarks/results/f1-doc-path-baseline/before-fix/4a-packaged-clean.stderr.txt +0 -0
  29. package/benchmarks/results/f1-doc-path-baseline/before-fix/4a-packaged-clean.stdout.json +115 -0
  30. package/benchmarks/results/f1-doc-path-baseline/before-fix/4b-packaged-injected.stderr.txt +0 -0
  31. package/benchmarks/results/f1-doc-path-baseline/before-fix/4b-packaged-injected.stdout.json +127 -0
  32. package/benchmarks/results/f1-doc-path-baseline/before-fix/5-source-missing-config.stderr.txt +0 -0
  33. package/benchmarks/results/f1-doc-path-baseline/before-fix/5-source-missing-config.stdout.json +15 -0
  34. package/benchmarks/results/f1-doc-path-baseline/before-fix/5b-packaged-missing-config.stderr.txt +0 -0
  35. package/benchmarks/results/f1-doc-path-baseline/before-fix/5b-packaged-missing-config.stdout.json +15 -0
  36. package/benchmarks/results/f1-doc-path-baseline/before-fix/6-supplementary-fixture-ok.input.md +8 -0
  37. package/benchmarks/results/f1-doc-path-baseline/before-fix/6-supplementary-fixture-ok.stdout.json +16 -0
  38. package/benchmarks/results/f1-doc-path-baseline/before-fix/README.md +34 -0
  39. package/benchmarks/results/f1-doc-path-baseline/probe-31-docs/after.json +248 -0
  40. package/benchmarks/results/f1-doc-path-baseline/probe-31-docs/before.json +254 -0
  41. package/benchmarks/results/f1-doc-path-baseline/probe-31-docs/probe.js +26 -0
  42. package/benchmarks/results/f2-doc-craft-baseline/README.md +32 -0
  43. package/benchmarks/results/f2-doc-craft-baseline/arm-f1-report.md +84 -0
  44. package/benchmarks/results/f2-doc-craft-baseline/arm-f1-task.md +21 -0
  45. package/benchmarks/results/f2-doc-craft-baseline/arm-f1.diff +19 -0
  46. package/benchmarks/results/f2-doc-craft-baseline/arm-f2-report.md +110 -0
  47. package/benchmarks/results/f2-doc-craft-baseline/arm-f2-task.md +21 -0
  48. package/benchmarks/results/f2-doc-craft-baseline/arm-f2.diff +19 -0
  49. package/benchmarks/results/f3-derived-views-baseline/README.md +34 -0
  50. package/benchmarks/results/f3-derived-views-baseline/arm-f2-report.md +43 -0
  51. package/benchmarks/results/f3-derived-views-baseline/arm-f2-task.md +21 -0
  52. package/benchmarks/results/f3-derived-views-baseline/arm-f2.diff +21 -0
  53. package/benchmarks/results/f3-derived-views-baseline/arm-f3-report.md +57 -0
  54. package/benchmarks/results/f3-derived-views-baseline/arm-f3-task.md +21 -0
  55. package/benchmarks/results/f3-derived-views-baseline/arm-f3.diff +21 -0
  56. package/benchmarks/results/live-host/claude-325-3b-s2b/README.md +99 -0
  57. package/benchmarks/results/live-host/claude-325-3b-s2b/capture.sanitized.jsonl +26 -0
  58. package/ci/archive/325-prep/README.md +71 -0
  59. package/ci/{prep → archive/325-prep}/doc-reconciliation-red.js +9 -7
  60. package/ci/archive/325-prep/f4-expected-red.json +34 -0
  61. package/ci/archive/325-prep/historical-qualification.test.js +143 -0
  62. package/ci/fixtures/doc-obligations/reconciliation-prep/cases.json +17 -4
  63. package/ci/fixtures/doc-obligations/reconciliation-prep/pending-red.json +8 -56
  64. package/ci/fixtures/doc-obligations/reconciliation-prep/trust-contract.json +2 -2
  65. package/ci/fixtures/doc-obligations/reconciliation-s1/cases.json +17 -5
  66. package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/harness.js +415 -0
  67. package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/off-handoff-golden.json +16 -0
  68. package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-post-tool-use-failure.json +11 -0
  69. package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-post-tool-use-success-minimal.json +9 -0
  70. package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-post-tool-use-success.json +13 -0
  71. package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-subagent-start.json +7 -0
  72. package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-subagent-stop-freetext-reject.json +11 -0
  73. package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-subagent-stop-reject.json +11 -0
  74. package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-subagent-stop.json +11 -0
  75. package/ci/fixtures/doc-obligations/reconciliation-s2b/capture/payload-task-result.json +19 -0
  76. package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/off-capture-only.json +99 -0
  77. package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/off-legacy.json +67 -0
  78. package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/on-concurrent-writers.json +62 -0
  79. package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/on-lookalikes-runtime-mutations.json +67 -0
  80. package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/on-malformed.json +85 -0
  81. package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/on-normal-session.json +74 -0
  82. package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/on-runtime-drift-later-npm-test.json +58 -0
  83. package/ci/fixtures/doc-obligations/reconciliation-s2b/corpus/on-valid-direct-quoted-review.json +85 -0
  84. package/ci/fixtures/doc-obligations/reconciliation-s2b/f4/cases.json +2267 -0
  85. package/ci/fixtures/doc-obligations/reconciliation-s2b/wiring/cases.json +1110 -0
  86. package/ci/fixtures/doc-obligations/reconciliation-s2b/wiring/legacy-off-golden.json +674 -0
  87. package/ci/lib/doc-reconciliation-file-spy.js +23 -0
  88. package/ci/lib/doc-reconciliation-prep-fixtures.js +136 -10
  89. package/ci/lib/doc-reconciliation-qualification-preload.js +37 -0
  90. package/ci/lib/doc-reconciliation-replay.js +104 -0
  91. package/ci/lib/doc-reconciliation-staged-tree.js +102 -0
  92. package/ci/mechanism-2p-opencode-v2-entrypoint.test.js +179 -0
  93. package/ci/mechanism-325-3b-preparation.test.js +27 -101
  94. package/ci/mechanism-325-3b-s2b-capture.test.js +561 -0
  95. package/ci/mechanism-325-3b-s2b-wiring.test.js +668 -0
  96. package/ci/mechanism-325-f1-doc-check.test.js +674 -0
  97. package/ci/mechanism-325-f2-doc-craft.test.js +597 -0
  98. package/ci/mechanism-325-f3-derived-views.test.js +798 -0
  99. package/ci/mechanism-325-f4-invariants.test.js +606 -0
  100. package/ci/mechanism-325-f4-projections.test.js +382 -0
  101. package/ci/mechanism-325-f4-qualification.test.js +228 -0
  102. package/ci/mechanism-325-f4-trust-boundary.test.js +880 -0
  103. package/ci/mechanism-365-step-edit-accounting.test.js +395 -0
  104. package/ci/mutation-check.js +26 -3
  105. package/docs/architecture.md +5 -0
  106. package/docs/doc-workflow-reconciliation.md +331 -4
  107. package/docs/document-lifecycle.md +11 -0
  108. package/docs/governed-index.md +20 -0
  109. package/docs/platform-capabilities.md +22 -2
  110. package/grill-with-docs/SKILL.md +1 -1
  111. package/grill-with-docs/references/session-playbook.md +4 -0
  112. package/harness-everything/SKILL.md +1 -1
  113. package/harness-everything/references/doc-check.md +186 -0
  114. package/harness-everything/references/documentation/_shared/maintenance.md +41 -0
  115. package/harness-everything/references/documentation/_shared/writing-craft.md +59 -0
  116. package/harness-everything/references/triage-and-tiers.md +4 -0
  117. package/harness-everything/scripts/doc-check.js +442 -0
  118. package/harness-everything/scripts/doc-lifecycle.js +3 -0
  119. package/hooks/hooks.json +46 -0
  120. package/hooks/scripts/lib/doc-reconciliation-capture.sanitized.jsonl +26 -0
  121. package/hooks/scripts/lib/doc-reconciliation-pilot.js +171 -2
  122. package/hooks/scripts/lib/doc-reconciliation-provider.js +1059 -0
  123. package/hooks/scripts/lib/doc-reconciliation-qualification.json +34 -0
  124. package/hooks/scripts/lib/workflow-obligations.js +7 -0
  125. package/hooks/scripts/state-persist.js +105 -3
  126. package/hooks/scripts/workflow-stop-gate.js +7 -2
  127. package/opencode-plugin/README.md +15 -0
  128. package/opencode-plugin/index.mjs +77 -0
  129. package/opencode-plugin/plugin.json +1 -1
  130. package/package.json +1 -1
  131. package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
  132. package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-capture.sanitized.jsonl +26 -0
  133. package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-pilot.js +171 -2
  134. package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-provider.js +1059 -0
  135. package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-qualification.json +34 -0
  136. package/plugins/harness-everything/hooks/scripts/lib/workflow-obligations.js +7 -0
  137. package/plugins/harness-everything/hooks/scripts/state-persist.js +105 -3
  138. package/plugins/harness-everything/hooks/scripts/workflow-stop-gate.js +7 -2
  139. package/plugins/harness-everything/plugin.json +1 -1
  140. package/plugins/harness-everything/skills/grill-with-docs/SKILL.md +1 -1
  141. package/plugins/harness-everything/skills/grill-with-docs/references/session-playbook.md +4 -0
  142. package/plugins/harness-everything/skills/harness-everything/SKILL.md +1 -1
  143. package/plugins/harness-everything/skills/harness-everything/references/doc-check.md +186 -0
  144. package/plugins/harness-everything/skills/harness-everything/references/documentation/_shared/maintenance.md +41 -0
  145. package/plugins/harness-everything/skills/harness-everything/references/documentation/_shared/writing-craft.md +59 -0
  146. package/plugins/harness-everything/skills/harness-everything/references/triage-and-tiers.md +4 -0
  147. package/plugins/harness-everything/skills/harness-everything/scripts/doc-check.js +442 -0
  148. package/plugins/harness-everything/skills/harness-everything/scripts/doc-lifecycle.js +3 -0
  149. package/plugins/harness-everything/skills/repo-docs/SKILL.md +1 -1
  150. package/plugins/harness-everything/skills/repo-docs/references/process-guide.md +4 -0
  151. package/plugins/harness-everything/skills/to-spec/SKILL.md +1 -1
  152. package/plugins/harness-everything/skills/to-spec/references/process.md +4 -0
  153. package/plugins/harness-everything/skills/to-tickets/SKILL.md +1 -1
  154. package/plugins/harness-everything/skills/to-tickets/references/publishing-and-templates.md +4 -0
  155. package/repo-docs/SKILL.md +1 -1
  156. package/repo-docs/references/process-guide.md +4 -0
  157. package/sbom.cdx.json +7 -7
  158. package/to-spec/SKILL.md +1 -1
  159. package/to-spec/references/process.md +4 -0
  160. package/to-tickets/SKILL.md +1 -1
  161. package/to-tickets/references/publishing-and-templates.md +4 -0
@@ -0,0 +1,442 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // Editorial document check: lifecycle lint (doc-lifecycle.js, run as a
5
+ // subprocess) plus local Markdown link targets in changed governed files.
6
+ // Prints one JSON result. Exit 0 = both pass, 1 = defect, 2 = usage/config error.
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const { execFileSync, spawnSync } = require('child_process');
10
+ const { collectDocs } = require('./doc-lifecycle');
11
+
12
+ // Logical command IDs (A3 section 3): stdout that repeats an ID is not trusted as credit by itself.
13
+ const CHECK_ID = 'harness.doc-check.check.v1';
14
+ const COMPARE_ID = 'harness.doc-check.compare-derived.v1';
15
+ const USAGE = 'usage: doc-check.js check --workspace <dir> --config <json-file, relative to --workspace> --changed-path <path> [--changed-path <path> ...] [--base <git-rev>]';
16
+ const VALUE_FLAGS = ['--workspace', '--config', '--changed-path', '--base'];
17
+ const EXTERNAL = /^(?:https?:|mailto:)/i;
18
+ const SCHEME = /^[a-z][a-z0-9+.-]*:/i;
19
+
20
+ function escaped(text, index) {
21
+ let slashes = 0;
22
+ for (let i = index - 1; i >= 0 && text[i] === '\\'; i--) slashes++;
23
+ return slashes % 2 === 1;
24
+ }
25
+
26
+ // Map each unescaped label opener to its closing bracket in one pass.
27
+ function labelCloses(text) {
28
+ const closes = new Array(text.length).fill(-1);
29
+ const opens = [];
30
+ for (let i = 0; i < text.length; i++) {
31
+ if ((text[i] !== '[' && text[i] !== ']') || escaped(text, i)) continue;
32
+ if (text[i] === '[') opens.push(i);
33
+ else if (opens.length) closes[opens.pop()] = i;
34
+ }
35
+ return closes;
36
+ }
37
+
38
+ function parseInlineLink(text, start, closes) {
39
+ const close = closes[start];
40
+ if (close < 0 || text[close + 1] !== '(') return null;
41
+
42
+ let i = close + 2;
43
+ while (/\s/.test(text[i] || '') && i < text.length) i++;
44
+ let target = '';
45
+ if (text[i] === '<') {
46
+ const begin = ++i;
47
+ while (i < text.length && (text[i] !== '>' || escaped(text, i))) i++;
48
+ if (i === text.length) return { invalid: true, end: i };
49
+ target = text.slice(begin, i++);
50
+ } else {
51
+ const begin = i;
52
+ let parens = 0;
53
+ while (i < text.length) {
54
+ if (escaped(text, i)) { i++; continue; }
55
+ if (text[i] === '(') parens++;
56
+ else if (text[i] === ')') {
57
+ if (parens === 0) break;
58
+ parens--;
59
+ } else if (/\s/.test(text[i])) break;
60
+ i++;
61
+ }
62
+ if (parens !== 0) return { invalid: true, end: i };
63
+ target = text.slice(begin, i);
64
+ }
65
+
66
+ while (/\s/.test(text[i] || '') && i < text.length) i++;
67
+ if (text[i] === '"' || text[i] === "'") {
68
+ const quote = text[i++];
69
+ while (i < text.length && (text[i] !== quote || escaped(text, i))) i++;
70
+ if (i === text.length) return { invalid: true, end: i };
71
+ i++;
72
+ while (/\s/.test(text[i] || '') && i < text.length) i++;
73
+ }
74
+ if (text[i] !== ')') return { invalid: true, end: i };
75
+ return { target: target.replace(/\\([\\`*{}\[\]()#+.!_>~-])/g, '$1'), end: i + 1 };
76
+ }
77
+
78
+ function parseArgs(argv) {
79
+ const [command, ...rest] = argv;
80
+ if (command !== 'check') throw new Error(USAGE);
81
+ const options = { changedPaths: [] };
82
+ for (let index = 0; index < rest.length; index += 2) {
83
+ const flag = rest[index];
84
+ const value = rest[index + 1];
85
+ if (!VALUE_FLAGS.includes(flag) || value === undefined) throw new Error(`${USAGE}\nunknown or incomplete argument: ${flag}`);
86
+ if (value.startsWith('--')) throw new Error(`${USAGE}\n${flag} requires a value, not a flag: ${value}`);
87
+ if (flag === '--changed-path') options.changedPaths.push(value);
88
+ else {
89
+ if (options[flag.slice(2)] !== undefined) throw new Error(`${USAGE}\nrepeated argument: ${flag}`);
90
+ options[flag.slice(2)] = value;
91
+ }
92
+ }
93
+ if (!options.workspace || !options.config) throw new Error(`${USAGE}\n--workspace and --config are required`);
94
+ if (!options.changedPaths.length) throw new Error(`${USAGE}\n--changed-path is required`);
95
+ return options;
96
+ }
97
+
98
+ function runLint(workspace, options) {
99
+ const args = [path.join(__dirname, 'doc-lifecycle.js'), 'lint', '--workspace', workspace, '--config', options.config];
100
+ for (const changed of options.changedPaths) args.push('--changed-path', changed);
101
+ if (options.base !== undefined) args.push('--base', options.base);
102
+ return spawnSync(process.execPath, args, { encoding: 'utf8' });
103
+ }
104
+
105
+ // Scan nested label spans depth-first, then resume after each destination. Matching brackets are precomputed per line,
106
+ // and an explicit stack keeps nesting from consuming the JS call stack. A nested link inside a label remains an
107
+ // intentional over-check: CommonMark keeps only the inner link, so both targets are checked and neither is silent.
108
+ function scanSpan(text, line, targets, unsupported) {
109
+ const closes = labelCloses(text);
110
+ const spans = [{ from: 0, to: text.length }];
111
+ while (spans.length) {
112
+ const span = spans.pop();
113
+ for (let i = span.from; i < span.to; i++) {
114
+ if (text[i] !== '[' || escaped(text, i)) continue;
115
+ const parsed = parseInlineLink(text, i, closes);
116
+ if (!parsed) continue;
117
+ if (parsed.invalid) unsupported.push({ line, syntax: 'inline-link-unparsed' });
118
+ else targets.push({ line, target: parsed.target });
119
+ if (parsed.end < span.to) spans.push({ from: parsed.end, to: span.to });
120
+ if (closes[i] > i + 1) spans.push({ from: i + 1, to: closes[i] });
121
+ break;
122
+ }
123
+ }
124
+ }
125
+
126
+ // Returns link targets outside fenced and inline code, plus link syntax this
127
+ // check does not evaluate. Unsupported syntax is reported, never skipped silently.
128
+ function scanLinks(lines) {
129
+ const targets = [];
130
+ const unsupported = [];
131
+ let fence = null;
132
+ lines.forEach((raw, index) => {
133
+ const line = index + 1;
134
+ const marker = raw.match(/^\s*(`{3,}|~{3,})/);
135
+ if (fence) {
136
+ if (marker && marker[1][0] === fence[0] && marker[1].length >= fence.length && raw.trim() === marker[1]) fence = null;
137
+ return;
138
+ }
139
+ if (marker) { fence = marker[1]; return; }
140
+ const text = raw.replace(/(`+)(.+?)\1/g, '');
141
+ scanSpan(text, line, targets, unsupported);
142
+ if (/\[[^\]]*\]\[[^\]]*\]/.test(text)) unsupported.push({ line, syntax: 'reference-link' });
143
+ if (/^\s{0,3}\[[^\]]+\]:/.test(text)) unsupported.push({ line, syntax: 'reference-definition' });
144
+ });
145
+ return { targets, unsupported };
146
+ }
147
+
148
+ function unsupportedSyntax(target) {
149
+ if (SCHEME.test(target)) return 'scheme';
150
+ if (target.startsWith('/')) return 'root-relative';
151
+ return 'scheme-less-url';
152
+ }
153
+
154
+ function decodeTarget(value) {
155
+ try { return decodeURI(value); } catch { return value; }
156
+ }
157
+
158
+ function emit(result, exit) {
159
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
160
+ return exit;
161
+ }
162
+
163
+ // A1 §3 line classifier over the body (frontmatter already removed). Returns prose and eligible physical-line counts.
164
+ function proseShare(body) {
165
+ let prose = 0;
166
+ let eligible = 0;
167
+ let fence = null;
168
+ for (const line of body.split(/\r?\n/)) {
169
+ if (fence) {
170
+ const close = line.match(/^ {0,3}(`{3,}|~{3,})\s*$/);
171
+ if (close && close[1][0] === fence[0] && close[1].length >= fence.length) fence = null;
172
+ continue;
173
+ }
174
+ const open = line.match(/^ {0,3}(`{3,}|~{3,})/);
175
+ if (open) { fence = open[1]; continue; }
176
+ if (!line.trim() || /^( {4}|\t)/.test(line)) continue;
177
+ eligible++;
178
+ if (!/^ {0,3}(#{1,6}(\s|$)|([-+*]|\d+[.)])\s|\||(-{3,}|\*{3,}|_{3,})\s*$)/.test(line)) prose++;
179
+ }
180
+ return { prose, eligible };
181
+ }
182
+
183
+ const COMPARE_FLAGS = ['--workspace', '--config', '--view', '--candidate', '--generation-status'];
184
+ const GENERATION_STATUSES = ['ok', 'failed', 'timeout', 'unavailable', 'unsupported'];
185
+ const COMPARE_EXIT = { equal: 0, stale: 1, 'missing-view': 1, 'generator-failed': 1, 'generation-timeout': 2, 'generator-unavailable': 2, 'comparison-unavailable': 2, unsupported: 2, 'usage-error': 2 };
186
+
187
+ function emitCompare(result) {
188
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
189
+ return COMPARE_EXIT[result.result];
190
+ }
191
+
192
+ function parseCompareArgs(argv) {
193
+ const options = {};
194
+ for (let index = 0; index < argv.length; index += 2) {
195
+ const flag = argv[index];
196
+ const value = argv[index + 1];
197
+ if (!COMPARE_FLAGS.includes(flag) || value === undefined || value.startsWith('--')) return { error: `unknown or incomplete argument: ${flag}` };
198
+ if (options[flag] !== undefined) return { error: `repeated argument: ${flag}` };
199
+ options[flag] = value;
200
+ }
201
+ return { options };
202
+ }
203
+
204
+ // Escape regex metacharacters, then translate `**`, `*` and `?`. Used only to detect a declared glob.
205
+ function globToRegExp(pattern) {
206
+ const body = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*\*/g, '\u0000').replace(/\*/g, '[^/]*').replace(/\?/g, '[^/]').replace(/\u0000/g, '.*');
207
+ return new RegExp(`^${body}$`);
208
+ }
209
+
210
+ // Same file when both paths stat to one device and inode, so symlinks and hard links are caught.
211
+ function sameFile(a, b) {
212
+ const x = fs.statSync(a);
213
+ const y = fs.statSync(b);
214
+ return x.ino > 0 && x.ino === y.ino && x.dev === y.dev;
215
+ }
216
+
217
+ // Open without blocking (O_NONBLOCK keeps a FIFO from waiting for a writer), then require a regular file before reading.
218
+ function readRegularFile(file) {
219
+ const fd = fs.openSync(file, fs.constants.O_RDONLY | (fs.constants.O_NONBLOCK || 0));
220
+ try {
221
+ if (!fs.fstatSync(fd).isFile()) throw new Error('not a regular file');
222
+ return fs.readFileSync(fd);
223
+ } finally {
224
+ fs.closeSync(fd);
225
+ }
226
+ }
227
+
228
+ // compare-derived: a pure byte comparison of a caller-captured generator output with a declared view.
229
+ // It never spawns the declared generator and never writes; every lane prints one JSON object.
230
+ function compareDerived(argv) {
231
+ // Loaded lazily so `check` keeps its load-time dependencies (doc-lifecycle.js only).
232
+ const { validateDocsConfig } = require('./project-docs-resolver');
233
+ const parsed = parseCompareArgs(argv);
234
+ const flags = parsed.options || {};
235
+ const base = { schema: COMPARE_ID, command: COMPARE_ID, view: flags['--view'] ?? null, generationStatus: flags['--generation-status'] ?? null };
236
+ if (parsed.error) return emitCompare({ ...base, result: 'usage-error', reason: parsed.error });
237
+ if (!flags['--workspace'] || !flags['--config'] || !flags['--view'] || !flags['--candidate']) return emitCompare({ ...base, result: 'usage-error', reason: '--workspace, --config, --view and --candidate are required' });
238
+ const status = flags['--generation-status'];
239
+ if (!GENERATION_STATUSES.includes(status)) return emitCompare({ ...base, result: 'comparison-unavailable', reason: '--generation-status is required: ok, failed, timeout, unavailable or unsupported' });
240
+ const view = flags['--view'];
241
+ const workspace = path.resolve(flags['--workspace']);
242
+ const configPath = path.resolve(workspace, flags['--config']);
243
+ let config;
244
+ try { config = JSON.parse(fs.readFileSync(configPath, 'utf8')); } catch { return emitCompare({ ...base, result: 'comparison-unavailable', reason: 'config is missing or not valid JSON' }); }
245
+ const validation = validateDocsConfig(config);
246
+ if (!validation.valid) return emitCompare({ ...base, result: 'comparison-unavailable', reason: `config is invalid: ${validation.diagnostics.map(item => item.code).join(', ')}` });
247
+ if (path.isAbsolute(view) || view.split(/[\\/]/).includes('..')) return emitCompare({ ...base, result: 'unsupported', reason: 'view must be a repo-relative path without traversal' });
248
+ if (/[*?[\]{}]/.test(view)) return emitCompare({ ...base, result: 'unsupported', reason: 'glob view selections are unsupported' });
249
+ const declared = Array.isArray(config.derived) ? config.derived : [];
250
+ const matches = declared.filter(entry => entry.path === view);
251
+ if (matches.length > 1) return emitCompare({ ...base, result: 'comparison-unavailable', reason: 'view is declared more than once (ambiguous selection)' });
252
+ if (matches.length === 0) {
253
+ const viaGlob = declared.some(entry => /[*?[\]{}]/.test(entry.path) && globToRegExp(entry.path).test(view));
254
+ const closest = declared.find(entry => normalizeView(entry.path) === normalizeView(view));
255
+ return emitCompare(viaGlob
256
+ ? { ...base, result: 'unsupported', reason: 'view matches a declared glob; only literal derived paths are supported' }
257
+ : { ...base, result: 'comparison-unavailable', reason: closest ? `view is not a declared derived path; the closest declared path is "${closest.path}"` : 'view is not a declared derived path' });
258
+ }
259
+ const target = path.join(workspace, view);
260
+ if (fs.existsSync(target) && !fs.realpathSync(target).startsWith(fs.realpathSync(workspace) + path.sep)) return emitCompare({ ...base, result: 'unsupported', reason: 'view resolves outside the workspace' });
261
+ if (status === 'failed') return emitCompare({ ...base, result: 'generator-failed', reason: 'generation reported failure; candidate not compared' });
262
+ if (status === 'timeout') return emitCompare({ ...base, result: 'generation-timeout', reason: 'generation timed out; candidate not compared' });
263
+ if (status === 'unavailable') return emitCompare({ ...base, result: 'generator-unavailable', reason: 'generator unavailable; candidate not compared' });
264
+ if (status === 'unsupported') return emitCompare({ ...base, result: 'unsupported', reason: 'generator reported unsupported (write-in-place or glob output); candidate not compared' });
265
+ const candidate = path.resolve(workspace, flags['--candidate']);
266
+ let candidateBytes;
267
+ try { candidateBytes = readRegularFile(candidate); } catch { return emitCompare({ ...base, result: 'comparison-unavailable', reason: 'candidate is missing, not a regular file, or unreadable' }); }
268
+ if (!fs.existsSync(target)) return emitCompare({ ...base, result: 'missing-view', reason: 'declared target file is absent' });
269
+ if (sameFile(candidate, target)) return emitCompare({ ...base, result: 'comparison-unavailable', reason: 'candidate aliases the target' });
270
+ const equal = Buffer.compare(candidateBytes, fs.readFileSync(target)) === 0;
271
+ return emitCompare({ ...base, result: equal ? 'equal' : 'stale', reason: equal ? 'candidate bytes equal the declared target' : 'candidate bytes differ from the declared target; target left unchanged' });
272
+ }
273
+
274
+ const GLOB_CHARS = /[*?[\]{}]/;
275
+ const VIEW_REASON = 'derived view not verified: run its authorized generator, then compare-derived';
276
+
277
+ // Near-miss comparison only (separators, leading ./, trailing /, case); never used to accept a view.
278
+ function normalizeView(value) {
279
+ return value.replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/+$/, '').toLowerCase();
280
+ }
281
+
282
+ // Declarations at the base revision, read with argv arrays (no shell). Null when unavailable.
283
+ function readBaseDeclarations(workspace, base, configRelative) {
284
+ if (base.startsWith('-')) return null;
285
+ try {
286
+ const text = execFileSync('git', ['show', `${base}:${configRelative}`], { cwd: workspace, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
287
+ const parsed = JSON.parse(text);
288
+ return Array.isArray(parsed.derived) ? parsed.derived : [];
289
+ } catch {
290
+ return null;
291
+ }
292
+ }
293
+
294
+ // F3 coverage. Declared views are unverified when a source, a derived output or the config changed. Declaration changes
295
+ // against --base are named; globs and self-governed views are named as unsupported. No generator is ever run.
296
+ function derivedCoverage(config, { workspace, configRelative, configChanged, affectsViews, base }) {
297
+ const declared = Array.isArray(config.derived) ? config.derived : [];
298
+ const governedPatterns = [...(config.governed || []), ...(config.legacy || [])]
299
+ .map(item => (typeof item === 'string' ? item : item && item.path))
300
+ .filter(item => typeof item === 'string');
301
+ const entries = [];
302
+ for (const view of new Set(declared.map(entry => entry.path))) {
303
+ if (GLOB_CHARS.test(view)) {
304
+ entries.push({ path: view, applicability: 'glob-unsupported', reason: 'glob declarations are unsupported; declare the concrete output path' });
305
+ continue;
306
+ }
307
+ if (affectsViews) entries.push({ path: view, state: 'unverified', applicability: 'conservative-all-governed', reason: VIEW_REASON });
308
+ if (governedPatterns.some(pattern => globToRegExp(pattern).test(view))) entries.push({ path: view, applicability: 'self-governed', reason: 'a derived view must not be governed or indexed by its own generator' });
309
+ }
310
+ if (base === undefined) {
311
+ if (configChanged) entries.push({ reason: 'config changed without --base; removed or renamed derived declarations cannot be detected' });
312
+ return entries;
313
+ }
314
+ const baseDeclared = readBaseDeclarations(workspace, base, configRelative);
315
+ if (baseDeclared === null) {
316
+ entries.push({ reason: `base config unavailable at ${base}; declarations cannot be compared` });
317
+ return entries;
318
+ }
319
+ for (const old of baseDeclared) {
320
+ if (GLOB_CHARS.test(old.path)) {
321
+ entries.push({ path: old.path, applicability: 'glob-unsupported', reason: `glob declaration at ${base}; glob declarations are unsupported` });
322
+ continue;
323
+ }
324
+ const current = declared.find(entry => entry.path === old.path);
325
+ if (!current) entries.push({ path: old.path, state: 'unverified', applicability: 'declaration-removed', reason: `declared at ${base}, absent from the current config; not verified` });
326
+ else if (current.generator !== old.generator) entries.push({ path: old.path, state: 'unverified', applicability: 'declaration-changed', reason: `generator changed since ${base}; not verified` });
327
+ }
328
+ return entries;
329
+ }
330
+
331
+ function check(options) {
332
+ const workspace = path.resolve(options.workspace);
333
+ const configPath = path.resolve(workspace, options.config);
334
+ const lint = runLint(workspace, options);
335
+ const lintExit = lint.status === 0 || lint.status === 1 ? lint.status : 2;
336
+ if (lintExit === 2) {
337
+ return emit({ schema: CHECK_ID, command: CHECK_ID, lint: { exit: 2, error: String(lint.stderr || lint.error || '').trim(), diagnostics: [] }, links: { checked: 0, broken: [], skipped: 'lint-usage-error' }, coverage: { unsupported: [] } }, 2);
338
+ }
339
+ let analysis;
340
+ try { analysis = JSON.parse(lint.stdout); } catch {
341
+ return emit({ schema: CHECK_ID, command: CHECK_ID, lint: { exit: 2, error: 'lint output was not JSON', diagnostics: [] }, links: { checked: 0, broken: [], skipped: 'lint-usage-error' }, coverage: { unsupported: [] } }, 2);
342
+ }
343
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
344
+ const docs = collectDocs(workspace, config);
345
+ const governed = new Set(docs.filter(doc => doc.role).map(doc => doc.path));
346
+
347
+ const broken = [];
348
+ const unsupported = [];
349
+ if (!Array.isArray(config.domains) || !config.domains.length) {
350
+ unsupported.push({ reason: 'no domains declared; bare <DOMAIN>-NNN references are not checked' });
351
+ }
352
+ let checked = 0;
353
+ for (const value of options.changedPaths) {
354
+ const relative = path.relative(workspace, path.resolve(workspace, value)).split(path.sep).join('/');
355
+ const absolute = path.join(workspace, relative);
356
+ if (!fs.existsSync(absolute)) {
357
+ unsupported.push({ path: relative, reason: 'not on disk' });
358
+ continue;
359
+ }
360
+ let text;
361
+ try { text = fs.readFileSync(absolute, 'utf8'); } catch {
362
+ broken.push({ path: relative, line: 0, target: null, reason: 'unreadable' });
363
+ continue;
364
+ }
365
+ if (!governed.has(relative)) {
366
+ unsupported.push({ path: relative, reason: 'not governed' });
367
+ continue;
368
+ }
369
+ const scan = scanLinks(text.split(/\r?\n/));
370
+ unsupported.push(...scan.unsupported.map(item => ({ path: relative, ...item })));
371
+ for (const { line, target } of scan.targets) {
372
+ if (target.startsWith('#') || EXTERNAL.test(target)) continue;
373
+ if (SCHEME.test(target) || target.startsWith('/') || /^www\./i.test(target)) {
374
+ unsupported.push({ path: relative, line, syntax: unsupportedSyntax(target), target });
375
+ continue;
376
+ }
377
+ checked++;
378
+ const file = decodeTarget(target.split('#')[0].split('?')[0]);
379
+ if (!file || !fs.existsSync(path.resolve(path.dirname(absolute), file))) broken.push({ path: relative, line, target });
380
+ }
381
+ }
382
+
383
+ // SHOULD reminders only: they never change the exit code and are never diagnostics.
384
+ const changed = new Set(options.changedPaths.map(value => path.relative(workspace, path.resolve(workspace, value)).split(path.sep).join('/')));
385
+ // F3 applicability (conservative): a governed or legacy source, an unknown or deleted path, a derived output or the config
386
+ // itself marks every declared view unverified. check still never runs a generator.
387
+ const configRelative = path.relative(workspace, configPath).split(path.sep).join('/');
388
+ const declaredViews = [...new Set((Array.isArray(config.derived) ? config.derived : []).map(entry => entry.path))];
389
+ const affectsViews = [...changed].some(item => {
390
+ if (item === configRelative || !fs.existsSync(path.join(workspace, item))) return true;
391
+ if (declaredViews.includes(item)) return true;
392
+ const doc = docs.find(entry => entry.path === item);
393
+ return Boolean(doc && (doc.role || doc.legacy));
394
+ });
395
+ unsupported.push(...derivedCoverage(config, { workspace, configRelative, configChanged: changed.has(configRelative), affectsViews, base: options.base }));
396
+
397
+ const reminders = [];
398
+ for (const doc of docs) {
399
+ if (!changed.has(doc.path) || doc.role !== 'reference' || doc.legacy || doc.error) continue;
400
+ const { prose, eligible } = proseShare(doc.body);
401
+ if (2 * prose > eligible) reminders.push({ code: 'prose-share', path: doc.path, strength: 'SHOULD', proseLines: prose, eligibleLines: eligible, share: prose / eligible, threshold: 0.5 });
402
+ }
403
+
404
+ // Per-capability reports (A3 section 3). Partial coverage is never reported as a full pass.
405
+ const governedChanged = [...changed].filter(item => governed.has(item));
406
+ const noDomains = !Array.isArray(config.domains) || !config.domains.length;
407
+ const linkSkipped = unsupported.filter(item => item.syntax || (item.path && item.reason && !item.applicability));
408
+ const result = {
409
+ schema: CHECK_ID,
410
+ command: CHECK_ID,
411
+ lint: { exit: lintExit, evidence: analysis.evidence, diagnostics: analysis.diagnostics },
412
+ links: { checked, broken },
413
+ coverage: { unsupported },
414
+ reminders,
415
+ capabilities: {
416
+ 'doc-lint': {
417
+ state: lintExit === 1 ? 'fail' : noDomains ? 'partial' : 'pass',
418
+ checkedPaths: governedChanged,
419
+ skipped: unsupported.filter(item => !item.path).map(item => item.reason),
420
+ },
421
+ 'link-check': {
422
+ state: broken.length ? 'fail' : linkSkipped.length ? 'partial' : 'pass',
423
+ checkedPaths: governedChanged,
424
+ skipped: linkSkipped,
425
+ },
426
+ }
427
+ };
428
+ return emit(result, lintExit === 1 || broken.length ? 1 : 0);
429
+ }
430
+
431
+ function main(argv = process.argv.slice(2)) {
432
+ if (argv[0] === 'compare-derived') return compareDerived(argv.slice(1));
433
+ let options;
434
+ try { options = parseArgs(argv); } catch (error) {
435
+ return emit({ lint: { exit: 2, error: error.message }, links: { checked: 0, broken: [], skipped: 'lint-usage-error' }, coverage: { unsupported: [] } }, 2);
436
+ }
437
+ return check(options);
438
+ }
439
+
440
+ module.exports = { main, scanLinks };
441
+
442
+ if (require.main === module) process.exitCode = main();
@@ -1290,6 +1290,9 @@ function lintDocs(docs, config, options = {}) {
1290
1290
  const ref = match[1].replace(/[.,;:!?]+$/, '');
1291
1291
  if (seen.has(ref)) continue;
1292
1292
  seen.add(ref);
1293
+ // A bare <X>-NNN is a reference only for a declared domain or an exact known ID, alias or legacy ID.
1294
+ const bare = ref.startsWith('REQ-') || ref.startsWith('PRD-') ? null : ref.match(/^([A-Z][A-Z0-9]{1,9})-\d{3,}/);
1295
+ if (bare && !domains.has(bare[1]) && !resolveRef(entries, ref, { graph }).doc && !entries.some(item => item.legacy && idFromLegacy(item) === ref)) continue;
1293
1296
  if (ref.startsWith('REQ-') && !allReqIds.has(ref)) {
1294
1297
  unresolved('missing-req', doc, ref, { error: 'missing-req', candidates: [] }, `requirement ${ref} is referenced but is not defined in an active PRD`);
1295
1298
  continue;
@@ -4,7 +4,7 @@ description: Generate or update precise, reader-oriented README.md and AGENTS.md
4
4
  license: Apache-2.0
5
5
  metadata:
6
6
  author: Miya Daniel
7
- version: 0.4.0
7
+ version: 0.41.0
8
8
  ---
9
9
 
10
10
  # Repo Docs (Automated Project Documentation Generation & Smart Merging)
@@ -1,5 +1,9 @@
1
1
  # Process Guide (Deep Dive)
2
2
 
3
+ Before drafting, read `<skills-repo-root>/harness-everything/references/documentation/_shared/writing-craft.md`; for governed-doc events, follow `<skills-repo-root>/harness-everything/references/documentation/_shared/maintenance.md`.
4
+ `<skills-repo-root>` is the directory that holds the sibling skill directories: the source checkout root, or `<plugin-root>/skills` when packaged.
5
+ If the harness-everything skill is not installed, say the shared reference is unavailable.
6
+
3
7
  ## Decision Flow
4
8
 
5
9
  ```mermaid
@@ -4,7 +4,7 @@ description: "Turn the conversation into a written spec — feature PRD, CLI/API
4
4
  license: Apache-2.0
5
5
  metadata:
6
6
  author: Miya Daniel
7
- version: 0.39.0
7
+ version: 0.41.0
8
8
  ---
9
9
 
10
10
  # To Spec
@@ -1,5 +1,9 @@
1
1
  # To Spec — Full Process Reference
2
2
 
3
+ Before drafting, read `<skills-repo-root>/harness-everything/references/documentation/_shared/writing-craft.md`; for governed-doc events, follow `<skills-repo-root>/harness-everything/references/documentation/_shared/maintenance.md`.
4
+ `<skills-repo-root>` is the directory that holds the sibling skill directories: the source checkout root, or `<plugin-root>/skills` when packaged.
5
+ If the harness-everything skill is not installed, say the shared reference is unavailable.
6
+
3
7
  ## Path Resolution Flow (Step 0)
4
8
 
5
9
  ```mermaid
@@ -4,7 +4,7 @@ description: Break a to-spec feature-spec, a plan, or a conversation into tracer
4
4
  license: Apache-2.0
5
5
  metadata:
6
6
  author: Miya Daniel
7
- version: 0.19.1
7
+ version: 0.41.0
8
8
  ---
9
9
 
10
10
  # To Tickets
@@ -1,5 +1,9 @@
1
1
  # Storage Path Resolution & Publishing Detail (moved from SKILL.md)
2
2
 
3
+ Before drafting, read `<skills-repo-root>/harness-everything/references/documentation/_shared/writing-craft.md`; for governed-doc events, follow `<skills-repo-root>/harness-everything/references/documentation/_shared/maintenance.md`.
4
+ `<skills-repo-root>` is the directory that holds the sibling skill directories: the source checkout root, or `<plugin-root>/skills` when packaged.
5
+ If the harness-everything skill is not installed, say the shared reference is unavailable.
6
+
3
7
  ### 0. Resolve Storage Path & Framework (Gated Resolution Loop)
4
8
 
5
9
  Follow the decision matrix below to determine where ticket artifacts must be published:
@@ -4,7 +4,7 @@ description: Generate or update precise, reader-oriented README.md and AGENTS.md
4
4
  license: Apache-2.0
5
5
  metadata:
6
6
  author: Miya Daniel
7
- version: 0.4.0
7
+ version: 0.41.0
8
8
  ---
9
9
 
10
10
  # Repo Docs (Automated Project Documentation Generation & Smart Merging)
@@ -1,5 +1,9 @@
1
1
  # Process Guide (Deep Dive)
2
2
 
3
+ Before drafting, read `<skills-repo-root>/harness-everything/references/documentation/_shared/writing-craft.md`; for governed-doc events, follow `<skills-repo-root>/harness-everything/references/documentation/_shared/maintenance.md`.
4
+ `<skills-repo-root>` is the directory that holds the sibling skill directories: the source checkout root, or `<plugin-root>/skills` when packaged.
5
+ If the harness-everything skill is not installed, say the shared reference is unavailable.
6
+
3
7
  ## Decision Flow
4
8
 
5
9
  ```mermaid
package/sbom.cdx.json CHANGED
@@ -2,23 +2,23 @@
2
2
  "$schema": "https://cyclonedx.org/schema/bom-1.7.schema.json",
3
3
  "bomFormat": "CycloneDX",
4
4
  "specVersion": "1.7",
5
- "serialNumber": "urn:uuid:ec1ab676-b0e1-5aa7-869d-8317e5674765",
5
+ "serialNumber": "urn:uuid:c067e2e8-259d-5b73-a1bf-780a0d33156e",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-10-23T07:24:05.300Z",
8
+ "timestamp": "2026-10-23T19:34:50.360Z",
9
9
  "tools": [
10
10
  {
11
11
  "vendor": "Harness Everything",
12
12
  "name": "generate-sbom.js",
13
- "version": "0.40.0"
13
+ "version": "0.41.1"
14
14
  }
15
15
  ],
16
16
  "component": {
17
17
  "type": "application",
18
- "bom-ref": "pkg:npm/harness-everything@0.40.0",
18
+ "bom-ref": "pkg:npm/harness-everything@0.41.1",
19
19
  "name": "harness-everything",
20
- "version": "0.40.0",
21
- "purl": "pkg:npm/harness-everything@0.40.0"
20
+ "version": "0.41.1",
21
+ "purl": "pkg:npm/harness-everything@0.41.1"
22
22
  }
23
23
  },
24
24
  "components": [
@@ -2033,7 +2033,7 @@
2033
2033
  ]
2034
2034
  },
2035
2035
  {
2036
- "ref": "pkg:npm/harness-everything@0.40.0",
2036
+ "ref": "pkg:npm/harness-everything@0.41.1",
2037
2037
  "dependsOn": [
2038
2038
  "pkg:npm/%40commitlint/cli@19.8.1",
2039
2039
  "pkg:npm/%40commitlint/config-conventional@19.8.1",
package/to-spec/SKILL.md CHANGED
@@ -4,7 +4,7 @@ description: "Turn the conversation into a written spec — feature PRD, CLI/API
4
4
  license: Apache-2.0
5
5
  metadata:
6
6
  author: Miya Daniel
7
- version: 0.39.0
7
+ version: 0.41.0
8
8
  ---
9
9
 
10
10
  # To Spec
@@ -1,5 +1,9 @@
1
1
  # To Spec — Full Process Reference
2
2
 
3
+ Before drafting, read `<skills-repo-root>/harness-everything/references/documentation/_shared/writing-craft.md`; for governed-doc events, follow `<skills-repo-root>/harness-everything/references/documentation/_shared/maintenance.md`.
4
+ `<skills-repo-root>` is the directory that holds the sibling skill directories: the source checkout root, or `<plugin-root>/skills` when packaged.
5
+ If the harness-everything skill is not installed, say the shared reference is unavailable.
6
+
3
7
  ## Path Resolution Flow (Step 0)
4
8
 
5
9
  ```mermaid
@@ -4,7 +4,7 @@ description: Break a to-spec feature-spec, a plan, or a conversation into tracer
4
4
  license: Apache-2.0
5
5
  metadata:
6
6
  author: Miya Daniel
7
- version: 0.19.1
7
+ version: 0.41.0
8
8
  ---
9
9
 
10
10
  # To Tickets
@@ -1,5 +1,9 @@
1
1
  # Storage Path Resolution & Publishing Detail (moved from SKILL.md)
2
2
 
3
+ Before drafting, read `<skills-repo-root>/harness-everything/references/documentation/_shared/writing-craft.md`; for governed-doc events, follow `<skills-repo-root>/harness-everything/references/documentation/_shared/maintenance.md`.
4
+ `<skills-repo-root>` is the directory that holds the sibling skill directories: the source checkout root, or `<plugin-root>/skills` when packaged.
5
+ If the harness-everything skill is not installed, say the shared reference is unavailable.
6
+
3
7
  ### 0. Resolve Storage Path & Framework (Gated Resolution Loop)
4
8
 
5
9
  Follow the decision matrix below to determine where ticket artifacts must be published: