ultimate-pi 0.20.0 → 0.22.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.
Files changed (130) hide show
  1. package/.agents/skills/harness-decisions/SKILL.md +68 -2
  2. package/.agents/skills/harness-git-commit/SKILL.md +72 -0
  3. package/.agents/skills/harness-governor/SKILL.md +2 -2
  4. package/.agents/skills/harness-ls-lint-setup/SKILL.md +59 -0
  5. package/.agents/skills/harness-plan/SKILL.md +13 -11
  6. package/.agents/skills/harness-review/SKILL.md +1 -1
  7. package/.agents/skills/harness-sentrux-repair/SKILL.md +48 -0
  8. package/.agents/skills/sentrux/SKILL.md +4 -2
  9. package/.agents/skills/wiki-save/SKILL.md +1 -1
  10. package/.pi/PACKAGING.md +6 -0
  11. package/.pi/SYSTEM.md +21 -3
  12. package/.pi/agents/harness/ls-lint-steward.md +49 -0
  13. package/.pi/agents/harness/planning/decompose.md +4 -4
  14. package/.pi/agents/harness/reviewing/evaluator.md +1 -1
  15. package/.pi/agents/harness/running/executor.md +1 -1
  16. package/.pi/agents/harness/sentrux-repair-advisor.md +50 -0
  17. package/.pi/agents/pi-pi/prompt-expert.md +17 -2
  18. package/.pi/auto-commit.json +9 -2
  19. package/.pi/extensions/debate-orchestrator.ts +3 -0
  20. package/.pi/extensions/harness-anchored-edit.ts +7 -9
  21. package/.pi/extensions/harness-ask-user.ts +13 -34
  22. package/.pi/extensions/harness-debate-tools.ts +43 -4
  23. package/.pi/extensions/harness-live-widget.ts +28 -19
  24. package/.pi/extensions/harness-run-context.ts +278 -115
  25. package/.pi/extensions/harness-web-tools.ts +598 -471
  26. package/.pi/extensions/ls-lint-rules-sync.ts +103 -0
  27. package/.pi/extensions/observation-bus.ts +4 -0
  28. package/.pi/extensions/policy-gate.ts +270 -229
  29. package/.pi/extensions/sentrux-rules-sync.ts +2 -0
  30. package/.pi/extensions/soundboard.ts +48 -48
  31. package/.pi/harness/README.md +4 -0
  32. package/.pi/harness/agents.manifest.json +15 -7
  33. package/.pi/harness/agents.policy.yaml +49 -82
  34. package/.pi/harness/docs/adrs/0052-ls-lint-naming-lifecycle.md +45 -0
  35. package/.pi/harness/docs/adrs/0052-sentrux-structured-repair.md +38 -0
  36. package/.pi/harness/docs/adrs/0053-plan-task-clarification-gate.md +39 -0
  37. package/.pi/harness/docs/adrs/0054-harness-native-ask-user.md +40 -0
  38. package/.pi/harness/docs/adrs/0055-auto-commit-coauthor-lifecycle.md +40 -0
  39. package/.pi/harness/docs/adrs/README.md +5 -0
  40. package/.pi/harness/docs/practice-map.md +10 -5
  41. package/.pi/harness/evals/smoke/ls-lint-stub.json +10 -0
  42. package/.pi/harness/evolution/self-healing-rules.json +16 -0
  43. package/.pi/harness/ls-lint/naming.manifest.json +128 -0
  44. package/.pi/harness/sentrux/architecture.manifest.json +1 -1
  45. package/.pi/harness/specs/auto-commit.schema.json +63 -0
  46. package/.pi/harness/specs/ls-lint-manifest-proposal.schema.json +80 -0
  47. package/.pi/harness/specs/ls-lint-signal.schema.json +47 -0
  48. package/.pi/harness/specs/naming-manifest.schema.json +54 -0
  49. package/.pi/harness/specs/plan-task-clarification.schema.json +88 -0
  50. package/.pi/harness/specs/sentrux-diagnostics.schema.json +173 -0
  51. package/.pi/harness/specs/sentrux-repair-plan.schema.json +133 -0
  52. package/.pi/harness/specs/sentrux-report.schema.json +119 -0
  53. package/.pi/harness/specs/sentrux-signal.schema.json +34 -1
  54. package/.pi/lib/agents-policy.d.mts +26 -51
  55. package/.pi/lib/agents-policy.mjs +41 -28
  56. package/.pi/lib/agt/build-evaluation-context.ts +136 -64
  57. package/.pi/lib/ask-user/constants.mjs +3 -0
  58. package/.pi/lib/ask-user/constants.ts +4 -0
  59. package/.pi/lib/ask-user/contracts/glimpse-parse.ts +56 -0
  60. package/.pi/lib/ask-user/contracts/glimpse-payload-build.ts +58 -0
  61. package/.pi/lib/ask-user/contracts/glimpse-payload.ts +38 -0
  62. package/.pi/lib/ask-user/core/questionnaire.ts +74 -0
  63. package/.pi/lib/ask-user/dialog.ts +2 -314
  64. package/.pi/lib/ask-user/fallback.ts +2 -78
  65. package/.pi/lib/ask-user/format.ts +85 -0
  66. package/.pi/lib/ask-user/glimpseui.d.ts +10 -0
  67. package/.pi/lib/ask-user/index.ts +114 -0
  68. package/.pi/lib/ask-user/merge-task-clarification.ts +98 -0
  69. package/.pi/lib/ask-user/policy.mjs +43 -0
  70. package/.pi/lib/ask-user/policy.ts +104 -0
  71. package/.pi/lib/ask-user/presenters/glimpse.ts +130 -0
  72. package/.pi/lib/ask-user/presenters/headless.ts +131 -0
  73. package/.pi/lib/ask-user/presenters/select.ts +60 -0
  74. package/.pi/lib/ask-user/presenters/tui.ts +373 -0
  75. package/.pi/lib/ask-user/presenters/types.ts +13 -0
  76. package/.pi/lib/ask-user/render.ts +40 -9
  77. package/.pi/lib/ask-user/schema.ts +66 -13
  78. package/.pi/lib/ask-user/types.ts +60 -3
  79. package/.pi/lib/ask-user/validate-core.mjs +193 -7
  80. package/.pi/lib/ask-user/validate.ts +53 -34
  81. package/.pi/lib/harness-anchored-edit/package.json +3 -0
  82. package/.pi/lib/harness-artifact-gate.ts +75 -21
  83. package/.pi/lib/harness-auto-commit-config.mjs +321 -0
  84. package/.pi/lib/harness-lens/clients/lsp/client.ts +62 -39
  85. package/.pi/lib/harness-lens/clients/tool-policy.ts +73 -181
  86. package/.pi/lib/harness-lens/index.ts +241 -108
  87. package/.pi/lib/harness-lens/tools/lsp-navigation.ts +10 -8
  88. package/.pi/lib/harness-repair-brief.ts +84 -25
  89. package/.pi/lib/harness-run-context.ts +42 -52
  90. package/.pi/lib/harness-sentrux-parse.mjs +272 -0
  91. package/.pi/lib/harness-sentrux-root.mjs +78 -0
  92. package/.pi/lib/harness-slash-completions.ts +116 -0
  93. package/.pi/lib/harness-spawn-topology.ts +121 -87
  94. package/.pi/lib/harness-subagent-submit-registry.ts +10 -0
  95. package/.pi/lib/harness-subagents-bridge.ts +4 -1
  96. package/.pi/lib/harness-ui-state.ts +95 -48
  97. package/.pi/lib/plan-approval/dialog.ts +5 -0
  98. package/.pi/lib/plan-approval/validate.ts +1 -1
  99. package/.pi/lib/plan-approval-readiness.ts +32 -0
  100. package/.pi/lib/plan-debate-gate.ts +154 -114
  101. package/.pi/lib/plan-task-clarification.ts +158 -0
  102. package/.pi/prompts/harness-auto.md +2 -2
  103. package/.pi/prompts/harness-ls-lint-steward.md +43 -0
  104. package/.pi/prompts/harness-plan.md +58 -8
  105. package/.pi/prompts/harness-review.md +40 -6
  106. package/.pi/prompts/harness-run.md +33 -11
  107. package/.pi/prompts/harness-setup.md +72 -3
  108. package/.pi/prompts/harness-steer.md +2 -1
  109. package/.pi/prompts/wiki-save.md +5 -4
  110. package/.pi/scripts/README.md +8 -0
  111. package/.pi/scripts/generate-agents-policy-yaml.mjs +14 -2
  112. package/.pi/scripts/harness-auto-commit-bootstrap.mjs +96 -0
  113. package/.pi/scripts/harness-cli-verify.sh +47 -0
  114. package/.pi/scripts/harness-git-churn.mjs +77 -0
  115. package/.pi/scripts/harness-git-commit.mjs +173 -0
  116. package/.pi/scripts/harness-ls-lint-bootstrap.mjs +142 -0
  117. package/.pi/scripts/harness-ls-lint-cli.mjs +184 -0
  118. package/.pi/scripts/harness-seed-project-contracts.mjs +47 -0
  119. package/.pi/scripts/harness-sentrux-diagnostics.mjs +230 -0
  120. package/.pi/scripts/harness-sentrux-report.mjs +256 -0
  121. package/.pi/scripts/harness-verify.mjs +288 -125
  122. package/.pi/scripts/ls-lint-rules-sync.mjs +265 -0
  123. package/.pi/scripts/run-tests.mjs +1 -0
  124. package/.pi/settings.example.json +1 -0
  125. package/.sentrux/rules.toml +1 -1
  126. package/AGENTS.md +1 -0
  127. package/CHANGELOG.md +25 -0
  128. package/README.md +13 -4
  129. package/package.json +5 -1
  130. package/vendor/pi-vcc/src/hooks/before-compact.ts +86 -60
@@ -59,6 +59,143 @@ async function countJsonlKinds(
59
59
  }
60
60
  }
61
61
 
62
+ async function collectStrategyErrors(args: {
63
+ runDir: string;
64
+ coverage: any;
65
+ reviewStrategy: any;
66
+ dialogueOpts: { max_exchanges_per_round: number };
67
+ }): Promise<string[]> {
68
+ const { runDir, coverage, reviewStrategy, dialogueOpts } = args;
69
+ const errors: string[] = [];
70
+ const consolidated = isConsolidatedReviewStrategy(reviewStrategy);
71
+ const parallelProbes = isParallelProbesReviewStrategy(reviewStrategy);
72
+
73
+ if (parallelProbes) {
74
+ for (const rel of laneArtifactPathsForParallelProbesRound()) {
75
+ if (!(await fileExists(join(runDir, rel)))) errors.push(`missing ${rel}`);
76
+ }
77
+ const messengerCheck = messengerRoundDebateReady(
78
+ await getMessengerRoundState(runDir, 1),
79
+ false,
80
+ dialogueOpts,
81
+ );
82
+ if (!messengerCheck.ok) {
83
+ for (const e of messengerCheck.errors) {
84
+ errors.push(`parallel_probes round messenger: ${e}`);
85
+ }
86
+ }
87
+ return errors;
88
+ }
89
+
90
+ if (consolidated) {
91
+ if (!(await fileExists(join(runDir, CONSOLIDATED_REVIEW_ARTIFACT)))) {
92
+ errors.push(`missing ${CONSOLIDATED_REVIEW_ARTIFACT}`);
93
+ }
94
+ for (const rel of laneArtifactPathsForConsolidatedRound()) {
95
+ if (!(await fileExists(join(runDir, rel)))) errors.push(`missing ${rel}`);
96
+ }
97
+ const messengerCheck = messengerRoundDebateReady(
98
+ await getMessengerRoundState(runDir, 1),
99
+ true,
100
+ dialogueOpts,
101
+ );
102
+ if (!messengerCheck.ok) {
103
+ for (const e of messengerCheck.errors) {
104
+ errors.push(`consolidated round messenger: ${e}`);
105
+ }
106
+ }
107
+ return errors;
108
+ }
109
+
110
+ const roundIndices = [
111
+ ...new Set(
112
+ Object.values(coverage.rounds_by_focus).filter(
113
+ (v): v is number => typeof v === "number",
114
+ ),
115
+ ),
116
+ ];
117
+ for (const r of roundIndices) {
118
+ const focus = coverage.focus_by_round[r] ?? null;
119
+ for (const rel of laneArtifactPathsForRound(r, focus)) {
120
+ if (!(await fileExists(join(runDir, rel)))) errors.push(`missing ${rel}`);
121
+ }
122
+ const messengerCheck = messengerRoundDebateReady(
123
+ await getMessengerRoundState(runDir, r),
124
+ focus === "quality" || r >= 4,
125
+ dialogueOpts,
126
+ );
127
+ if (!messengerCheck.ok) {
128
+ for (const e of messengerCheck.errors) {
129
+ errors.push(`round ${r} messenger: ${e}`);
130
+ }
131
+ }
132
+ }
133
+ return errors;
134
+ }
135
+
136
+ async function collectBusAndConsensusIssues(args: {
137
+ debateId: string;
138
+ debatesDir: string;
139
+ caps: ReturnType<typeof capsForDebate>;
140
+ requiredFocuses: readonly PlanDebateFocus[];
141
+ coverage: any;
142
+ debateProfile: string;
143
+ }): Promise<{ errors: string[]; warnings: string[] }> {
144
+ const {
145
+ debateId,
146
+ debatesDir,
147
+ caps,
148
+ requiredFocuses,
149
+ coverage,
150
+ debateProfile,
151
+ } = args;
152
+ const errors: string[] = [];
153
+ const warnings: string[] = [];
154
+ const { rounds, hasConsensus } = await countJsonlKinds(
155
+ join(debatesDir, `${debateId}.jsonl`),
156
+ );
157
+ if (rounds < caps.min_focus_rounds) {
158
+ errors.push(
159
+ `${debateId}.jsonl has ${rounds}/${caps.min_focus_rounds} minimum round events — use harness_debate_submit_round per focus`,
160
+ );
161
+ }
162
+ if (!hasConsensus)
163
+ errors.push(
164
+ `missing consensus on ${debateId} — call harness_debate_consensus`,
165
+ );
166
+ if (
167
+ !planDebateOutcomeComplete(coverage, {
168
+ requiredFocuses,
169
+ minRoundIndex: caps.min_focus_rounds,
170
+ })
171
+ ) {
172
+ errors.push(
173
+ `debate outcome incomplete: required focuses [${requiredFocuses.join(", ")}] with last review_gate_ready true (profile=${debateProfile})`,
174
+ );
175
+ }
176
+ const consensusPath = join(debatesDir, `${debateId}.consensus.json`);
177
+ if (!(await fileExists(consensusPath))) {
178
+ errors.push(`missing ${debateId}.consensus.json`);
179
+ } else {
180
+ try {
181
+ const packet = JSON.parse(await readFile(consensusPath, "utf-8")) as {
182
+ policy_decision?: string;
183
+ };
184
+ if (packet.policy_decision === "block") {
185
+ errors.push("consensus policy_decision is block — cannot approve");
186
+ }
187
+ } catch {
188
+ errors.push("invalid consensus json");
189
+ }
190
+ }
191
+ if (rounds > caps.max_rounds) {
192
+ warnings.push(
193
+ `bus round count ${rounds} exceeds soft max_rounds ${caps.max_rounds}`,
194
+ );
195
+ }
196
+ return { errors, warnings };
197
+ }
198
+
62
199
  export interface PlanDebateGateResult {
63
200
  ok: boolean;
64
201
  errors: string[];
@@ -129,76 +266,14 @@ export async function validatePlanDebateGate(
129
266
  errors.push("last submitted review round has review_gate_ready !== true");
130
267
  }
131
268
 
132
- if (parallelProbes) {
133
- for (const rel of laneArtifactPathsForParallelProbesRound()) {
134
- const abs = join(runDir, rel);
135
- if (!(await fileExists(abs))) {
136
- errors.push(`missing ${rel}`);
137
- }
138
- }
139
- const roundState = await getMessengerRoundState(runDir, 1);
140
- const messengerCheck = messengerRoundDebateReady(
141
- roundState,
142
- false,
269
+ errors.push(
270
+ ...(await collectStrategyErrors({
271
+ runDir,
272
+ coverage,
273
+ reviewStrategy,
143
274
  dialogueOpts,
144
- );
145
- if (!messengerCheck.ok) {
146
- for (const e of messengerCheck.errors) {
147
- errors.push(`parallel_probes round messenger: ${e}`);
148
- }
149
- }
150
- } else if (consolidated) {
151
- const absConsolidated = join(runDir, CONSOLIDATED_REVIEW_ARTIFACT);
152
- if (!(await fileExists(absConsolidated))) {
153
- errors.push(`missing ${CONSOLIDATED_REVIEW_ARTIFACT}`);
154
- }
155
- for (const rel of laneArtifactPathsForConsolidatedRound()) {
156
- const abs = join(runDir, rel);
157
- if (!(await fileExists(abs))) {
158
- errors.push(`missing ${rel}`);
159
- }
160
- }
161
- const roundState = await getMessengerRoundState(runDir, 1);
162
- const messengerCheck = messengerRoundDebateReady(
163
- roundState,
164
- true,
165
- dialogueOpts,
166
- );
167
- if (!messengerCheck.ok) {
168
- for (const e of messengerCheck.errors) {
169
- errors.push(`consolidated round messenger: ${e}`);
170
- }
171
- }
172
- } else {
173
- const roundIndices = [
174
- ...new Set(
175
- Object.values(coverage.rounds_by_focus).filter(
176
- (v): v is number => typeof v === "number",
177
- ),
178
- ),
179
- ];
180
- for (const r of roundIndices) {
181
- const focus = coverage.focus_by_round[r] ?? null;
182
- for (const rel of laneArtifactPathsForRound(r, focus)) {
183
- const abs = join(runDir, rel);
184
- if (!(await fileExists(abs))) {
185
- errors.push(`missing ${rel}`);
186
- }
187
- }
188
- const roundState = await getMessengerRoundState(runDir, r);
189
- const requireSprint = focus === "quality" || r >= 4;
190
- const messengerCheck = messengerRoundDebateReady(
191
- roundState,
192
- requireSprint,
193
- dialogueOpts,
194
- );
195
- if (!messengerCheck.ok) {
196
- for (const e of messengerCheck.errors) {
197
- errors.push(`round ${r} messenger: ${e}`);
198
- }
199
- }
200
- }
201
- }
275
+ })),
276
+ );
202
277
 
203
278
  if (
204
279
  isHarnessBudgetEnforceOn() &&
@@ -224,51 +299,16 @@ export async function validatePlanDebateGate(
224
299
  errors.push(`messenger debate_id ${messenger.debate_id} !== ${debateId}`);
225
300
  }
226
301
 
227
- const jsonlPath = join(debatesDir, `${debateId}.jsonl`);
228
- const { rounds, hasConsensus } = await countJsonlKinds(jsonlPath);
229
- const minRounds = caps.min_focus_rounds;
230
- if (rounds < minRounds) {
231
- errors.push(
232
- `${debateId}.jsonl has ${rounds}/${minRounds} minimum round events — use harness_debate_submit_round per focus`,
233
- );
234
- }
235
- if (!hasConsensus) {
236
- errors.push(
237
- `missing consensus on ${debateId} — call harness_debate_consensus`,
238
- );
239
- }
240
-
241
- if (
242
- !planDebateOutcomeComplete(coverage, {
243
- requiredFocuses,
244
- minRoundIndex: caps.min_focus_rounds,
245
- })
246
- ) {
247
- errors.push(
248
- `debate outcome incomplete: required focuses [${requiredFocuses.join(", ")}] with last review_gate_ready true (profile=${debateProfile})`,
249
- );
250
- }
251
-
252
- const consensusPath = join(debatesDir, `${debateId}.consensus.json`);
253
- if (!(await fileExists(consensusPath))) {
254
- errors.push(`missing ${debateId}.consensus.json`);
255
- } else {
256
- try {
257
- const raw = await readFile(consensusPath, "utf-8");
258
- const packet = JSON.parse(raw) as { policy_decision?: string };
259
- if (packet.policy_decision === "block") {
260
- errors.push("consensus policy_decision is block — cannot approve");
261
- }
262
- } catch {
263
- errors.push("invalid consensus json");
264
- }
265
- }
266
-
267
- if (rounds > caps.max_rounds) {
268
- warnings.push(
269
- `bus round count ${rounds} exceeds soft max_rounds ${caps.max_rounds}`,
270
- );
271
- }
302
+ const busChecks = await collectBusAndConsensusIssues({
303
+ debateId,
304
+ debatesDir,
305
+ caps,
306
+ requiredFocuses,
307
+ coverage,
308
+ debateProfile,
309
+ });
310
+ errors.push(...busChecks.errors);
311
+ warnings.push(...busChecks.warnings);
272
312
 
273
313
  return {
274
314
  ok: errors.length === 0,
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Task clarification artifact (Phase 0) — readiness, hashing, write-order guards.
3
+ */
4
+
5
+ import { createHash } from "node:crypto";
6
+ import { constants } from "node:fs";
7
+ import { access, readFile } from "node:fs/promises";
8
+ import { join } from "node:path";
9
+ import { parse as parseYaml } from "yaml";
10
+
11
+ export const TASK_CLARIFICATION_ARTIFACT = "artifacts/task-clarification.yaml";
12
+
13
+ /** Plan artifacts blocked until task clarification is ready. */
14
+ export const PLAN_ARTIFACTS_REQUIRING_CLARIFICATION = new Set([
15
+ "artifacts/planning-context.yaml",
16
+ "artifacts/decomposition.yaml",
17
+ "artifacts/hypothesis.yaml",
18
+ "artifacts/implementation-research.yaml",
19
+ "artifacts/stack.yaml",
20
+ "artifacts/plan-phase-status.yaml",
21
+ "artifacts/plan-phase-waiver.yaml",
22
+ "artifacts/execution-plan-draft.yaml",
23
+ "artifacts/sentrux-manifest-proposal.yaml",
24
+ "artifacts/ls-lint-manifest-proposal.yaml",
25
+ "research-brief.yaml",
26
+ "plan-packet.yaml",
27
+ ]);
28
+
29
+ export function isPlanArtifactRequiringClarification(relPath: string): boolean {
30
+ const normalized = relPath.replace(/\\/g, "/");
31
+ if (PLAN_ARTIFACTS_REQUIRING_CLARIFICATION.has(normalized)) {
32
+ return true;
33
+ }
34
+ if (
35
+ /^artifacts\/review-round(-r\d+|-consolidated)?\.yaml$/i.test(normalized)
36
+ ) {
37
+ return true;
38
+ }
39
+ if (
40
+ /^artifacts\/(adversary|evaluator|sprint-audit|validation)-.*\.yaml$/i.test(
41
+ normalized,
42
+ )
43
+ ) {
44
+ return true;
45
+ }
46
+ return false;
47
+ }
48
+
49
+ export function computeTaskInputHash(input: {
50
+ sourceTask: string;
51
+ riskLevel?: string;
52
+ quick?: boolean;
53
+ }): string {
54
+ const payload = [
55
+ input.sourceTask.trim(),
56
+ String(input.riskLevel ?? "").toLowerCase(),
57
+ input.quick ? "quick" : "",
58
+ ].join("\n");
59
+ return createHash("sha256")
60
+ .update(payload, "utf8")
61
+ .digest("hex")
62
+ .slice(0, 16);
63
+ }
64
+
65
+ async function fileExists(path: string): Promise<boolean> {
66
+ try {
67
+ await access(path, constants.R_OK);
68
+ return true;
69
+ } catch {
70
+ return false;
71
+ }
72
+ }
73
+
74
+ export async function readTaskClarificationDoc(
75
+ runDir: string,
76
+ ): Promise<Record<string, unknown> | null> {
77
+ const path = join(runDir, TASK_CLARIFICATION_ARTIFACT);
78
+ if (!(await fileExists(path))) return null;
79
+ try {
80
+ const raw = await readFile(path, "utf-8");
81
+ const doc = parseYaml(raw) as unknown;
82
+ return doc && typeof doc === "object" && !Array.isArray(doc)
83
+ ? (doc as Record<string, unknown>)
84
+ : null;
85
+ } catch {
86
+ return null;
87
+ }
88
+ }
89
+
90
+ export interface TaskClarificationReadiness {
91
+ ok: boolean;
92
+ errors: string[];
93
+ }
94
+
95
+ export function validateTaskClarificationDoc(
96
+ doc: Record<string, unknown> | null,
97
+ opts?: { requireReady?: boolean },
98
+ ): TaskClarificationReadiness {
99
+ const errors: string[] = [];
100
+ if (!doc) {
101
+ errors.push(`missing ${TASK_CLARIFICATION_ARTIFACT}`);
102
+ return { ok: false, errors };
103
+ }
104
+ const status = String(doc.status ?? "").toLowerCase();
105
+ const requireReady = opts?.requireReady !== false;
106
+ if (requireReady && status !== "ready") {
107
+ errors.push(
108
+ `${TASK_CLARIFICATION_ARTIFACT}: status must be ready (got "${status || "missing"}")`,
109
+ );
110
+ }
111
+ const clarified = String(doc.clarified_task ?? "").trim();
112
+ if (requireReady && clarified.length < 8) {
113
+ errors.push(
114
+ `${TASK_CLARIFICATION_ARTIFACT}: clarified_task too short or missing`,
115
+ );
116
+ }
117
+ const unresolved = doc.unresolved_questions;
118
+ if (requireReady) {
119
+ if (!Array.isArray(unresolved)) {
120
+ errors.push(
121
+ `${TASK_CLARIFICATION_ARTIFACT}: unresolved_questions must be an array`,
122
+ );
123
+ } else if (unresolved.length > 0) {
124
+ errors.push(
125
+ `${TASK_CLARIFICATION_ARTIFACT}: unresolved_questions must be empty before ready (${unresolved.length} remaining)`,
126
+ );
127
+ }
128
+ }
129
+ return { ok: errors.length === 0, errors };
130
+ }
131
+
132
+ export async function isTaskClarificationReady(
133
+ runDir: string,
134
+ ): Promise<TaskClarificationReadiness> {
135
+ const doc = await readTaskClarificationDoc(runDir);
136
+ return validateTaskClarificationDoc(doc, { requireReady: true });
137
+ }
138
+
139
+ export async function assertTaskClarificationReadyForPlanWrite(
140
+ runDir: string,
141
+ relPath: string,
142
+ ): Promise<{ ok: boolean; message?: string }> {
143
+ const normalized = relPath.replace(/\\/g, "/");
144
+ if (normalized === TASK_CLARIFICATION_ARTIFACT) {
145
+ return { ok: true };
146
+ }
147
+ if (!isPlanArtifactRequiringClarification(normalized)) {
148
+ return { ok: true };
149
+ }
150
+ const readiness = await isTaskClarificationReady(runDir);
151
+ if (!readiness.ok) {
152
+ return {
153
+ ok: false,
154
+ message: `Blocked: ${normalized} requires ${TASK_CLARIFICATION_ARTIFACT} with status ready. ${readiness.errors.join("; ")}`,
155
+ };
156
+ }
157
+ return { ok: true };
158
+ }
@@ -20,11 +20,11 @@ If task missing:
20
20
 
21
21
  Follow **harness-plan** performance rules (`subagent` with `agentScope: "both"`). Use parallel `tasks` only for Phase 3.5 research (≤2 lanes) when subprocesses are needed. Never parallelize decompose∥hypothesis or debate lanes — precheck enforces this.
22
22
 
23
- 1. **Plan** — follow `/harness-plan` (context → lakes/synthesis or sequential framing → research → plan-verify → `approve_plan()` + `create_plan()`). One approval.
23
+ 1. **Plan** — follow `/harness-plan` (task clarification gate → context → lakes/synthesis or sequential framing → research → plan-verify → `approve_plan()` + `create_plan()`). One approval.
24
24
  2. **Execute** — `harness/running/executor` with `executor_strategy` from packet (default `single_pass` for low/med).
25
25
  3. **Review** — always **`/harness-review`** after execute (no benchmark fail-fast).
26
26
  4. **Steer loop** — while `review-outcome.remediation_class === implementation_gap` and `steer_attempt < HARNESS_STEER_MAX_ATTEMPTS`: `/harness-steer` → `/harness-review` (tiered adversary on attempts 2+).
27
- 5. **Parent** — apply locked strict gates; commit/PR only when `remediation_class: pass`.
27
+ 5. **Parent** — apply locked strict gates; commit/PR only when `remediation_class: pass`. For commits, invoke **harness-git-commit** skill (never raw `git commit`).
28
28
 
29
29
  Do **not** call separate `/harness-eval` or `/harness-critic` (deprecated aliases of `/harness-review`).
30
30
 
@@ -0,0 +1,43 @@
1
+ ---
2
+ description: Ad-hoc naming convention review — spawn harness/ls-lint-steward with graphify evidence.
3
+ argument-hint: "[--run <run-id>]"
4
+ ---
5
+
6
+ # harness-ls-lint-steward
7
+
8
+ You are the **chair** for ls-lint **intent** evolution (manifest → `.ls-lint.yml`). Spawn **`harness/ls-lint-steward`** only — do not edit the manifest inline without a proposal artifact.
9
+
10
+ **Skill:** `harness-ls-lint-setup` — bootstrap vs steward vs sync.
11
+
12
+ ## When to run
13
+
14
+ - Plan or run adds paths/extensions not covered by `naming.manifest.json`
15
+ - Post-run `ls-lint` failures suggesting missing scoped rules (before replan)
16
+ - User requests naming convention change
17
+
18
+ ## Spawn
19
+
20
+ ```
21
+ subagent({
22
+ agentScope: "both",
23
+ agent: "harness/ls-lint-steward",
24
+ task: "<HarnessSpawnContext + planning-context + execution-plan-draft + ls-lint output if any>"
25
+ })
26
+ ```
27
+
28
+ Gate: `harness_artifact_ready({ paths: ["artifacts/ls-lint-manifest-proposal.yaml"] })`
29
+
30
+ ## Chair applies (after `human_required` cleared)
31
+
32
+ Read `artifacts/ls-lint-manifest-proposal.yaml`.
33
+
34
+ If `change_class` is `none`, report and stop.
35
+
36
+ Otherwise:
37
+
38
+ 1. Apply `manifest_patch` to `.pi/harness/ls-lint/naming.manifest.json`.
39
+ 2. `node "$UP_PKG/.pi/scripts/harness-ls-lint-bootstrap.mjs" --force`
40
+ 3. Append session entry `harness-naming-changed` (triggers extension sync on `agent_end`).
41
+ 4. Optional: `node "$UP_PKG/.pi/scripts/harness-ls-lint-cli.mjs"` to confirm pass.
42
+
43
+ Report `change_class`, whether manifest was updated, and ls-lint outcome if run after sync.
@@ -11,6 +11,8 @@ You are the **planning orchestrator** (agent-native; ADR 0042). Produce an execu
11
11
 
12
12
  Subagents persist artifacts via scoped **`submit_*`** tools (deterministic YAML under the run dir). Parent uses **`harness_artifact_ready`** to gate phases (no JSON parsing). Parent merges still use **`write_harness_yaml`** for `research-brief.yaml`, `plan-packet.yaml`, `planning-context.yaml`, and integrator patches.
13
13
 
14
+ **Phase 0 is mandatory** before reconnaissance or any planning subagent. `write_harness_yaml` and spawn topology enforce `artifacts/task-clarification.yaml` with `status: ready` (ADR 0053).
15
+
14
16
  ## Allowed subagents
15
17
 
16
18
  - `harness/planning/planning-context` (optional — prefer parent tools for Phase 1)
@@ -53,22 +55,53 @@ Read **harness-debate-plan** skill before Review Gate rounds.
53
55
 
54
56
  Use `[HarnessActivePlan]` / `[HarnessRunContext]` only. On revise: preserve `plan_id` / `task_id`. Canonical paths: `plan-packet.yaml`, `research-brief.yaml`, `artifacts/*.yaml`.
55
57
 
56
- ## Phase 0 — Tooling / fast feedback (automatic)
58
+ ## Phase 0 — Task clarification (mandatory; parent-led)
59
+
60
+ **Practice:** Collect requirements / pool of shared meaning before WBS (PMBOK; Crucial Conversations). **ADR 0053.**
61
+
62
+ **Goal:** `artifacts/task-clarification.yaml` with `status: ready`, `unresolved_questions: []`, and a canonical `clarified_task`. No full planning until gated.
63
+
64
+ ### Phase 0a — Tooling (automatic)
65
+
66
+ Do **not** run `ccc index` or `ccc search --refresh`. Incremental `ccc index` runs before subagent spawns when you use subprocesses later.
67
+
68
+ ### Allowed during Phase 0
69
+
70
+ - **Codebase:** `Read`, `sg -p`, `ccc search`, `graphify query` / `explain` / `path`, `GRAPH_REPORT.md` — to disambiguate what the user wants and what “done” means
71
+ - **Web:** **web-retrieval** — linked specs, APIs, tickets (disambiguate the **task**, not Phase 3.5 landscape/stack commitment)
72
+ - **`ask_user`** — harness-decisions; **one tool call per clarification round** (flat `options` or questionnaire `questions[]`, ≤8 sub-questions). Prefer questionnaire when scope, success, and risk are independent. After answers, merge via `applyAskUserToTaskClarification` — do not hand-edit structured YAML fields.
73
+
74
+ Prefer minimum investigation; codebase and web are **not** forbidden.
75
+
76
+ ### Not allowed until `task-clarification` is `ready`
77
+
78
+ - Any **`subagent`** spawn
79
+ - `artifacts/planning-context.yaml`, `decomposition.yaml`, `hypothesis.yaml`, Phase 3.5 research artifacts, `plan-packet.yaml`, debate rounds, `approve_plan` / `create_plan`, DAG validation, Review Gate
80
+
81
+ ### Algorithm
57
82
 
58
- **Practice:** Invest in iteration speed (Pragmatic Programmer).
83
+ 1. Parse task + `--risk` / `--quick`.
84
+ 2. **`mode: revise`:** If `artifacts/task-clarification.yaml` exists with `status: ready` and `task_input_hash` matches current args (hash = source task + risk + quick flag), skip to Phase 1. If `last_outcome` is `needs_clarification`, do **not** skip.
85
+ 3. Investigate as needed; log `grounding` + `evidence_refs` on the artifact.
86
+ 4. Draft `artifacts/task-clarification.yaml` via `write_harness_yaml` (`schema_version: "1.0.0"`, fields per `plan-task-clarification.schema.json`). Set `task_input_hash` from source task + flags. List `unresolved_questions` when scope, success criteria, risk, or target surface are ambiguous.
87
+ 5. While `unresolved_questions` non-empty → `ask_user` (batch related forks in one call when possible); merge answers into `task-clarification.yaml` using the merge helper; increment `clarification_rounds`. On cancel → `plan_status: needs_clarification` and stop.
88
+ 6. When ready → `status: ready`, empty `unresolved_questions`, copy `acceptance_checks_draft`, set `risk_level` (CLI `--risk` wins when provided).
89
+ 7. Gate: `harness_artifact_ready({ paths: ["artifacts/task-clarification.yaml"] })` — updates `task_summary` to `clarified_task` when valid.
59
90
 
60
- Do **not** run `ccc index` or `ccc search --refresh`. The harness runs incremental `ccc index` before subagent spawns when you use subprocesses. Proceed to Phase 1.
91
+ **`--quick`:** Same gate. At most **one** `ask_user` tool call (questionnaire allowed) when the task already states explicit acceptance; if still ambiguous after that round, set `needs_clarification` and **do not** enter Phase 1.
61
92
 
62
93
  ## Phase 1 — Reconnaissance before WBS (parent-led, default)
63
94
 
64
95
  **Practice:** Shared context before scope decomposition — use the right tools for the job (graphify → sg → ccc → read per `AGENTS.md`).
65
96
 
66
- **Default (no subprocess):** As parent, gather reconnaissance with tools as needed for the task:
97
+ **Requires** Phase 0 gate. Read `artifacts/task-clarification.yaml` first; set `task_ref: artifacts/task-clarification.yaml` on planning context.
98
+
99
+ **Default (no subprocess):** Extend Phase 0 grounding — do **not** repeat `evidence_refs` or re-fetch URLs unless scope changed after `ask_user`:
67
100
 
68
101
  1. Read `graphify-out/GRAPH_REPORT.md` when present; use `graphify query` / `explain` / `path` for architecture and cross-module relationships.
69
102
  2. Use `sg -p '…'` for structural surfaces (handlers, types, exports).
70
103
  3. Use `ccc search` for semantic implementation matches (unless `--quick` — set `coverage.semantic.status: skipped`).
71
- 4. Write `artifacts/planning-context.yaml` via `write_harness_yaml` with `schema_version: "1.0.0"`, `status`, `summary`, `coverage` (architecture + structure required; semantic per risk/quick), `findings`, `evidence_refs`, `open_questions`.
104
+ 4. Write `artifacts/planning-context.yaml` via `write_harness_yaml` with `schema_version: "1.0.0"`, `status`, `summary`, `coverage` (architecture + structure required; semantic per risk/quick), `findings`, `evidence_refs`, `open_questions` (**technical** unknowns only — do not re-ask scope closed in Phase 0).
72
105
 
73
106
  **Optional subprocess:** Spawn **at most one** `harness/planning/planning-context` when the brief is large or you need context isolation.
74
107
 
@@ -79,12 +112,12 @@ Gate: `harness_artifact_ready({ paths: ["artifacts/planning-context.yaml"] })`.
79
112
  **Practice:** PMBOK scope / WBS; Berkun — how the team divides work.
80
113
 
81
114
  ```
82
- subagent({ agentScope: "both", agent: "harness/planning/decompose", task: "<HarnessSpawnContext + path to planning-context.yaml>" })
115
+ subagent({ agentScope: "both", agent: "harness/planning/decompose", task: "<HarnessSpawnContext + planning-context.yaml + task-clarification.yaml (clarified_task, in_scope, out_of_scope, acceptance_checks_draft)>" })
83
116
  ```
84
117
 
85
118
  Gate: `harness_artifact_ready({ paths: ["artifacts/decomposition.yaml"] })`.
86
119
 
87
- Decompose **prior_art** is **internal only** (from Phase 1). External prior art arrives in Phase 3.5.
120
+ Decompose treats **`task-clarification.yaml` as authoritative** for scope; §1.1 is **delta-only** (tensions/gaps), not a second full restatement. **prior_art** is **internal only** (from Phase 1). External prior art arrives in Phase 3.5.
88
121
 
89
122
  ## Phase 2b — Hypothesis-driven approach (sequential)
90
123
 
@@ -137,7 +170,9 @@ Build draft `PlanPacket` (`contract_version: "1.1.0"`):
137
170
 
138
171
  Initialize `research-brief.yaml` with decomposition + hypothesis + Phase 3.5 merges (`write_harness_yaml`).
139
172
 
140
- **`ask_user` on material `dialectical_fork`** after Phase 3.5 merge (evidence-backed conflicting external patterns may trigger `human_required` from eligibility).
173
+ Copy `acceptance_checks` from `task-clarification.acceptance_checks_draft` unless debate patches change them.
174
+
175
+ **`ask_user` on material `dialectical_fork`** after Phase 3.5 merge (evidence-backed research fork — **not** a substitute for Phase 0 task contract).
141
176
 
142
177
  ## Phase 4b — Schedule + WBS detail
143
178
 
@@ -179,6 +214,21 @@ If `change_class` ≠ `none` and `human_required` → `ask_user` before manifest
179
214
 
180
215
  Do **not** spawn on every plan or when changes stay inside existing layer globs.
181
216
 
217
+ ### Phase 4e′ — Naming intent (optional)
218
+
219
+ Spawn **`harness/ls-lint-steward`** when **any** apply (after Phase 4b, before Phase 4c):
220
+
221
+ - Execution plan adds top-level paths or file types not covered by `naming.manifest.json`
222
+ - Prior run reported `ls-lint` failures on new directories or extensions
223
+
224
+ ```
225
+ subagent({ agentScope: "both", agent: "harness/ls-lint-steward", task: "<HarnessSpawnContext + planning-context + execution-plan-draft + scope paths>" })
226
+ ```
227
+
228
+ Gate: `harness_artifact_ready({ paths: ["artifacts/ls-lint-manifest-proposal.yaml"] })`.
229
+
230
+ If `change_class` ≠ `none` and `human_required` → `ask_user` before manifest edits. Chair applies patch, runs `harness-ls-lint-bootstrap.mjs --force`, emits `harness-naming-changed`. See `/harness-ls-lint-steward`.
231
+
182
232
  ## Phase 4d — Tailor process to risk
183
233
 
184
234
  **Practice:** PMBOK tailoring.