@jwilger/pi-development-system 0.66.0 → 0.68.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/package.json
CHANGED
package/prompts/devsys-plan.md
CHANGED
|
@@ -8,5 +8,5 @@ Write the plan for the current work with the work-intake-and-slicing skill.
|
|
|
8
8
|
2. Write `docs/plan/<slug>.md` with these sections: Goal and why; Constraints (what must not change); Increments, each shippable on its own and small enough to release, each with an **Acceptance** list (observable checks that say the increment is done) and a final Release step (commit, push, CI green, publish, update) followed by a STOP for the user; for every task a task record whose header is `## <id> — <title>` and whose sections are written exactly as `**Goal:**`, `**Files:**`, `**Interfaces:**`, `**First failing test:**`, `**Steps:**`, `**Run:**`, `**Expected:**`, `**Out of scope:**` (label and colon inside the bold, one section per label); a Progress checklist with one box per increment.
|
|
9
9
|
3. Write each task so that someone who sees only that task could finish it. Never write `TBD`.
|
|
10
10
|
4. Run `devsys_task_check` on every task record you wrote and fix each one until it says ready. Split any that come back too-big.
|
|
11
|
-
5.
|
|
12
|
-
6.
|
|
11
|
+
5. Stop and ask the user to review the plan before any implementation starts. Do not start a goal yet: a goal tool may continue on its own and begin implementing before the review.
|
|
12
|
+
6. Only after the user approves the plan: if a `create_goal` tool exists in this session, call it with the objective "complete docs/plan/<slug>.md honouring its Constraints and Progress checklist, one increment at a time, stopping after each increment's Release step". Otherwise tell the user the plan path so they can start a goal on it. Do not depend on any goal tool's file format.
|
package/prompts/devsys-start.md
CHANGED
|
@@ -7,4 +7,4 @@ Start new work with the work-intake-and-slicing skill.
|
|
|
7
7
|
1. Call `devsys_intake` with `request` set to: $ARGUMENTS (if empty, ask the user what they want done first).
|
|
8
8
|
2. Show the user the proposed size and artifacts. The tool asks them to confirm or change the size.
|
|
9
9
|
3. Follow the recommended artifacts in order. To skip one, call `devsys_record_departure` with gate `artifact.skipped:<artifact>` and the reason; do not skip silently. The event model and architecture are recommendations, never gates.
|
|
10
|
-
4. For `fix` and `change` the phase is already `implementing`: write the task record, then work it test-first.
|
|
10
|
+
4. For a confirmed `fix` the user is also asked whether to skip fresh-context review (it is logged if they say yes; if no, the slice needs its review rounds). For `fix` and `change` the phase is already `implementing`: write the task record, then work it test-first.
|
|
@@ -20,7 +20,7 @@ Ask these in order and stop at the first yes:
|
|
|
20
20
|
|
|
21
21
|
## Artifacts follow size
|
|
22
22
|
|
|
23
|
-
- `fix`: a task record.
|
|
23
|
+
- `fix`: a task record. The user is asked separately whether to waive fresh-context review for it; a yes is logged as a user-approved `review.unsatisfied` departure for that slice, a no keeps the review gate.
|
|
24
24
|
- `change`: task record, review, an ADR if a decision is hard to reverse.
|
|
25
25
|
- `capability`: add a brief-lite, journeys, an event model, and optionally lens review.
|
|
26
26
|
- `product`: the full set, including brief, decision register and architecture.
|
|
@@ -43,9 +43,44 @@ const slicesInUse = (state: DevsysState): Set<string> =>
|
|
|
43
43
|
...state.openDepartures.flatMap((d) => (d.scope.kind === "slice" ? [d.scope.slice] : [])),
|
|
44
44
|
]);
|
|
45
45
|
|
|
46
|
+
/** The waiver is its own question, naming the request and what the review gate actually covers. */
|
|
47
|
+
const askToWaiveReview = (
|
|
48
|
+
ctx: ExtensionContext,
|
|
49
|
+
request: string,
|
|
50
|
+
slice: SliceRef,
|
|
51
|
+
): Promise<boolean> =>
|
|
52
|
+
ctx.ui.confirm(
|
|
53
|
+
"Skip fresh-context review for this fix?",
|
|
54
|
+
`"${request.slice(0, 120)}"\nThe review gate checks the whole uncommitted diff, so yes waives review for everything committed under slice "${slice}". The waiver is logged in docs/decisions.`,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
/** Starting new work replaces the active slice; while it is in flight that needs the user's yes. */
|
|
58
|
+
async function declinedToReplace(
|
|
59
|
+
ctx: ExtensionContext,
|
|
60
|
+
before: DevsysState,
|
|
61
|
+
): Promise<string | undefined> {
|
|
62
|
+
const { activeSlice, phase } = before;
|
|
63
|
+
if (activeSlice === undefined || (phase !== "implementing" && phase !== "reviewing")) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
const go = await ctx.ui.confirm(
|
|
67
|
+
"Slice already in flight",
|
|
68
|
+
`Slice "${activeSlice}" is still ${phase}. Its uncommitted changes would be committed under the new slice's rules. Start new work anyway?`,
|
|
69
|
+
);
|
|
70
|
+
return go ? undefined : `Intake cancelled; slice "${activeSlice}" is unchanged.`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const waiverNote = (sizing: Sizing, departed: string | undefined): string => {
|
|
74
|
+
if (departed !== undefined)
|
|
75
|
+
return `\nRecorded in ${departed}: you waived fresh-context review for this fix.`;
|
|
76
|
+
return sizing === "fix"
|
|
77
|
+
? "\nReview is NOT waived: this slice still needs its review rounds before commit."
|
|
78
|
+
: "";
|
|
79
|
+
};
|
|
80
|
+
|
|
46
81
|
/**
|
|
47
|
-
* I8.1 sizes a fix without a review artifact, but the commit gate reviews every slice (I6.4).
|
|
48
|
-
*
|
|
82
|
+
* I8.1 sizes a fix without a review artifact, but the commit gate reviews every slice (I6.4). Only the user's
|
|
83
|
+
* explicit yes to the waiver question is recorded, as a user-approved departure for this slice alone.
|
|
49
84
|
*/
|
|
50
85
|
async function fixWithoutReview(
|
|
51
86
|
deps: { pi: ExtensionAPI; state: SessionState },
|
|
@@ -60,7 +95,7 @@ async function fixWithoutReview(
|
|
|
60
95
|
tier: "soft",
|
|
61
96
|
default: info?.default ?? "finish the fresh-context review before release",
|
|
62
97
|
chosen: "no review rounds for this slice",
|
|
63
|
-
why: "the user
|
|
98
|
+
why: "the user was asked and agreed: this is a fix, which the sizing table plans without a review artifact",
|
|
64
99
|
costIfWrong: "a defect in a small change ships without a fresh-context look",
|
|
65
100
|
approver: "user",
|
|
66
101
|
scope: { kind: "slice", slice },
|
|
@@ -112,19 +147,20 @@ export function createIntakeTool(deps: {
|
|
|
112
147
|
if (picked === undefined) return reply(`${proposal}\nIntake cancelled; nothing changed.`);
|
|
113
148
|
const sizing = parseSizing(picked);
|
|
114
149
|
if (isParseError(sizing)) return reply(sizing.message, true);
|
|
115
|
-
const
|
|
150
|
+
const before = deps.state.get();
|
|
151
|
+
const keep = await declinedToReplace(ctx, before);
|
|
152
|
+
if (keep !== undefined) return reply(`${proposal}\n${keep}`);
|
|
153
|
+
const slice = uniqueSlice(sliceSlug(request), slicesInUse(before)) as SliceRef;
|
|
116
154
|
deps.state.update((s) => ({
|
|
117
155
|
...s,
|
|
118
156
|
phase: phaseFor(sizing),
|
|
119
157
|
sizing,
|
|
120
158
|
activeSlice: slice,
|
|
121
159
|
}));
|
|
122
|
-
const
|
|
160
|
+
const waived = sizing === "fix" ? await askToWaiveReview(ctx, request, slice) : false;
|
|
161
|
+
const departed = waived ? await fixWithoutReview(deps, ctx, slice) : undefined;
|
|
123
162
|
const final = renderProposal({ sizing, basis, proposal: proposalFor(sizing) });
|
|
124
|
-
const note =
|
|
125
|
-
departed === undefined
|
|
126
|
-
? ""
|
|
127
|
-
: `\nRecorded in ${departed}: no review rounds for this fix (you sized it fix).`;
|
|
163
|
+
const note = waiverNote(sizing, departed);
|
|
128
164
|
return reply(`${final}\nPhase: ${phaseFor(sizing)}. Active slice: ${slice}.${note}`);
|
|
129
165
|
},
|
|
130
166
|
};
|
|
@@ -29,6 +29,15 @@ const insideRepo = (cwd: string, target: string): boolean => {
|
|
|
29
29
|
return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
+
/** Jev's aspect keys, named as the record's own sections so the author knows what to edit. */
|
|
33
|
+
const SECTION_OF: Readonly<Record<string, string>> = {
|
|
34
|
+
goal: "Goal",
|
|
35
|
+
interfaces: "Interfaces",
|
|
36
|
+
firstFailingTest: "First failing test",
|
|
37
|
+
steps: "Steps",
|
|
38
|
+
check: "Run and Expected",
|
|
39
|
+
};
|
|
40
|
+
|
|
32
41
|
/** `devsys_task_check`: structural readiness is deterministic; Jev adds "specific enough" and "one task". */
|
|
33
42
|
export function createTaskCheckTool(deps: {
|
|
34
43
|
jev: (ctx: ExtensionContext) => Jev;
|
|
@@ -72,7 +81,8 @@ export function createTaskCheckTool(deps: {
|
|
|
72
81
|
`${record.id}: too-big. Split it into task records that each need one failing test, then check each.`,
|
|
73
82
|
);
|
|
74
83
|
}
|
|
75
|
-
|
|
84
|
+
const named = missing.map((m) => SECTION_OF[m] ?? m).join(", ");
|
|
85
|
+
return reply(`${record.id}: needs-detail. Make these more specific: ${named}.`);
|
|
76
86
|
},
|
|
77
87
|
};
|
|
78
88
|
}
|
package/src/tracker/github.ts
CHANGED
|
@@ -226,9 +226,10 @@ const updateIssue = async (
|
|
|
226
226
|
const current = toItem(raw.value);
|
|
227
227
|
const hadMarker = (raw.value.labels ?? []).some((l) => isMarker(l.name));
|
|
228
228
|
if (patch.status === "in-progress") {
|
|
229
|
-
// No --force: that would recolour the team's existing label.
|
|
230
|
-
|
|
231
|
-
|
|
229
|
+
// No --force: that would recolour the team's existing label. Creating needs push rights, which someone who can
|
|
230
|
+
// only triage lacks even when the label exists, so a failed create is not fatal: the edit below fails if the
|
|
231
|
+
// label is really missing, and says so.
|
|
232
|
+
await gh(["label", "create", IN_PROGRESS]);
|
|
232
233
|
}
|
|
233
234
|
const flags = editFlags(current, hadMarker, patch);
|
|
234
235
|
if (flags.length > 0) {
|