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.
- package/.agents/skills/harness-decisions/SKILL.md +68 -2
- package/.agents/skills/harness-git-commit/SKILL.md +72 -0
- package/.agents/skills/harness-governor/SKILL.md +2 -2
- package/.agents/skills/harness-ls-lint-setup/SKILL.md +59 -0
- package/.agents/skills/harness-plan/SKILL.md +13 -11
- package/.agents/skills/harness-review/SKILL.md +1 -1
- package/.agents/skills/harness-sentrux-repair/SKILL.md +48 -0
- package/.agents/skills/sentrux/SKILL.md +4 -2
- package/.agents/skills/wiki-save/SKILL.md +1 -1
- package/.pi/PACKAGING.md +6 -0
- package/.pi/SYSTEM.md +21 -3
- package/.pi/agents/harness/ls-lint-steward.md +49 -0
- package/.pi/agents/harness/planning/decompose.md +4 -4
- package/.pi/agents/harness/reviewing/evaluator.md +1 -1
- package/.pi/agents/harness/running/executor.md +1 -1
- package/.pi/agents/harness/sentrux-repair-advisor.md +50 -0
- package/.pi/agents/pi-pi/prompt-expert.md +17 -2
- package/.pi/auto-commit.json +9 -2
- package/.pi/extensions/debate-orchestrator.ts +3 -0
- package/.pi/extensions/harness-anchored-edit.ts +7 -9
- package/.pi/extensions/harness-ask-user.ts +13 -34
- package/.pi/extensions/harness-debate-tools.ts +43 -4
- package/.pi/extensions/harness-live-widget.ts +28 -19
- package/.pi/extensions/harness-run-context.ts +278 -115
- package/.pi/extensions/harness-web-tools.ts +598 -471
- package/.pi/extensions/ls-lint-rules-sync.ts +103 -0
- package/.pi/extensions/observation-bus.ts +4 -0
- package/.pi/extensions/policy-gate.ts +270 -229
- package/.pi/extensions/sentrux-rules-sync.ts +2 -0
- package/.pi/extensions/soundboard.ts +48 -48
- package/.pi/harness/README.md +4 -0
- package/.pi/harness/agents.manifest.json +15 -7
- package/.pi/harness/agents.policy.yaml +49 -82
- package/.pi/harness/docs/adrs/0052-ls-lint-naming-lifecycle.md +45 -0
- package/.pi/harness/docs/adrs/0052-sentrux-structured-repair.md +38 -0
- package/.pi/harness/docs/adrs/0053-plan-task-clarification-gate.md +39 -0
- package/.pi/harness/docs/adrs/0054-harness-native-ask-user.md +40 -0
- package/.pi/harness/docs/adrs/0055-auto-commit-coauthor-lifecycle.md +40 -0
- package/.pi/harness/docs/adrs/README.md +5 -0
- package/.pi/harness/docs/practice-map.md +10 -5
- package/.pi/harness/evals/smoke/ls-lint-stub.json +10 -0
- package/.pi/harness/evolution/self-healing-rules.json +16 -0
- package/.pi/harness/ls-lint/naming.manifest.json +128 -0
- package/.pi/harness/sentrux/architecture.manifest.json +1 -1
- package/.pi/harness/specs/auto-commit.schema.json +63 -0
- package/.pi/harness/specs/ls-lint-manifest-proposal.schema.json +80 -0
- package/.pi/harness/specs/ls-lint-signal.schema.json +47 -0
- package/.pi/harness/specs/naming-manifest.schema.json +54 -0
- package/.pi/harness/specs/plan-task-clarification.schema.json +88 -0
- package/.pi/harness/specs/sentrux-diagnostics.schema.json +173 -0
- package/.pi/harness/specs/sentrux-repair-plan.schema.json +133 -0
- package/.pi/harness/specs/sentrux-report.schema.json +119 -0
- package/.pi/harness/specs/sentrux-signal.schema.json +34 -1
- package/.pi/lib/agents-policy.d.mts +26 -51
- package/.pi/lib/agents-policy.mjs +41 -28
- package/.pi/lib/agt/build-evaluation-context.ts +136 -64
- package/.pi/lib/ask-user/constants.mjs +3 -0
- package/.pi/lib/ask-user/constants.ts +4 -0
- package/.pi/lib/ask-user/contracts/glimpse-parse.ts +56 -0
- package/.pi/lib/ask-user/contracts/glimpse-payload-build.ts +58 -0
- package/.pi/lib/ask-user/contracts/glimpse-payload.ts +38 -0
- package/.pi/lib/ask-user/core/questionnaire.ts +74 -0
- package/.pi/lib/ask-user/dialog.ts +2 -314
- package/.pi/lib/ask-user/fallback.ts +2 -78
- package/.pi/lib/ask-user/format.ts +85 -0
- package/.pi/lib/ask-user/glimpseui.d.ts +10 -0
- package/.pi/lib/ask-user/index.ts +114 -0
- package/.pi/lib/ask-user/merge-task-clarification.ts +98 -0
- package/.pi/lib/ask-user/policy.mjs +43 -0
- package/.pi/lib/ask-user/policy.ts +104 -0
- package/.pi/lib/ask-user/presenters/glimpse.ts +130 -0
- package/.pi/lib/ask-user/presenters/headless.ts +131 -0
- package/.pi/lib/ask-user/presenters/select.ts +60 -0
- package/.pi/lib/ask-user/presenters/tui.ts +373 -0
- package/.pi/lib/ask-user/presenters/types.ts +13 -0
- package/.pi/lib/ask-user/render.ts +40 -9
- package/.pi/lib/ask-user/schema.ts +66 -13
- package/.pi/lib/ask-user/types.ts +60 -3
- package/.pi/lib/ask-user/validate-core.mjs +193 -7
- package/.pi/lib/ask-user/validate.ts +53 -34
- package/.pi/lib/harness-anchored-edit/package.json +3 -0
- package/.pi/lib/harness-artifact-gate.ts +75 -21
- package/.pi/lib/harness-auto-commit-config.mjs +321 -0
- package/.pi/lib/harness-lens/clients/lsp/client.ts +62 -39
- package/.pi/lib/harness-lens/clients/tool-policy.ts +73 -181
- package/.pi/lib/harness-lens/index.ts +241 -108
- package/.pi/lib/harness-lens/tools/lsp-navigation.ts +10 -8
- package/.pi/lib/harness-repair-brief.ts +84 -25
- package/.pi/lib/harness-run-context.ts +42 -52
- package/.pi/lib/harness-sentrux-parse.mjs +272 -0
- package/.pi/lib/harness-sentrux-root.mjs +78 -0
- package/.pi/lib/harness-slash-completions.ts +116 -0
- package/.pi/lib/harness-spawn-topology.ts +121 -87
- package/.pi/lib/harness-subagent-submit-registry.ts +10 -0
- package/.pi/lib/harness-subagents-bridge.ts +4 -1
- package/.pi/lib/harness-ui-state.ts +95 -48
- package/.pi/lib/plan-approval/dialog.ts +5 -0
- package/.pi/lib/plan-approval/validate.ts +1 -1
- package/.pi/lib/plan-approval-readiness.ts +32 -0
- package/.pi/lib/plan-debate-gate.ts +154 -114
- package/.pi/lib/plan-task-clarification.ts +158 -0
- package/.pi/prompts/harness-auto.md +2 -2
- package/.pi/prompts/harness-ls-lint-steward.md +43 -0
- package/.pi/prompts/harness-plan.md +58 -8
- package/.pi/prompts/harness-review.md +40 -6
- package/.pi/prompts/harness-run.md +33 -11
- package/.pi/prompts/harness-setup.md +72 -3
- package/.pi/prompts/harness-steer.md +2 -1
- package/.pi/prompts/wiki-save.md +5 -4
- package/.pi/scripts/README.md +8 -0
- package/.pi/scripts/generate-agents-policy-yaml.mjs +14 -2
- package/.pi/scripts/harness-auto-commit-bootstrap.mjs +96 -0
- package/.pi/scripts/harness-cli-verify.sh +47 -0
- package/.pi/scripts/harness-git-churn.mjs +77 -0
- package/.pi/scripts/harness-git-commit.mjs +173 -0
- package/.pi/scripts/harness-ls-lint-bootstrap.mjs +142 -0
- package/.pi/scripts/harness-ls-lint-cli.mjs +184 -0
- package/.pi/scripts/harness-seed-project-contracts.mjs +47 -0
- package/.pi/scripts/harness-sentrux-diagnostics.mjs +230 -0
- package/.pi/scripts/harness-sentrux-report.mjs +256 -0
- package/.pi/scripts/harness-verify.mjs +288 -125
- package/.pi/scripts/ls-lint-rules-sync.mjs +265 -0
- package/.pi/scripts/run-tests.mjs +1 -0
- package/.pi/settings.example.json +1 -0
- package/.sentrux/rules.toml +1 -1
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +25 -0
- package/README.md +13 -4
- package/package.json +5 -1
- 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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
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
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
|
|
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 —
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
**
|
|
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 +
|
|
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
|
-
|
|
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.
|