@chorus-aidlc/chorus-pi 0.17.2 → 0.18.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/README.md +87 -0
- package/agents/chorus-code-reviewer.md +3 -1
- package/agents/chorus-proposal-reviewer.md +5 -3
- package/agents/chorus-task-reviewer.md +6 -1
- package/bin/chorus-mcp-call.sh +1 -1
- package/extensions/chorus.ts +139 -30
- package/lib/lib.ts +201 -53
- package/package.json +5 -2
- package/skills/brainstorm/SKILL.md +1 -1
- package/skills/chorus/SKILL.md +10 -7
- package/skills/chorus-cli/SKILL.md +1 -1
- package/skills/develop/SKILL.md +6 -4
- package/skills/docs/SKILL.md +1 -1
- package/skills/idea/SKILL.md +1 -1
- package/skills/openspec-aware/SKILL.md +31 -39
- package/skills/orchestrate/SKILL.md +9 -1
- package/skills/proposal/SKILL.md +14 -7
- package/skills/quick-dev/SKILL.md +2 -2
- package/skills/review/SKILL.md +4 -4
- package/skills/spec-lite/SKILL.md +149 -0
- package/skills/yolo/SKILL.md +29 -17
package/lib/lib.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pure helpers extracted from the chorus-pi extension for unit testing.
|
|
3
3
|
*
|
|
4
|
-
* These functions hold no mutable state and (except for
|
|
4
|
+
* These functions hold no mutable state and (except for resolveSpecMode, which
|
|
5
5
|
* takes injectable fs/execSync) have no I/O — so they can be tested without a
|
|
6
6
|
* running Pi session or a live Chorus instance. The extension imports them
|
|
7
7
|
* from here; tests import the same functions.
|
|
@@ -11,7 +11,7 @@ import { dirname, join } from "node:path";
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Minimal fs surface needed by the config readers below.
|
|
14
|
-
* (
|
|
14
|
+
* (resolveSpecMode already uses FsLike; keep this as the shared type.)
|
|
15
15
|
*/
|
|
16
16
|
export interface FsLike {
|
|
17
17
|
existsSync(p: string): boolean;
|
|
@@ -183,68 +183,211 @@ export function sessionWorkflow(sessionUuid: string): string {
|
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
/**
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
186
|
+
* True when a worker task already carries an injected session block.
|
|
187
|
+
*
|
|
188
|
+
* Matches the block header at the start of a line (`--- Chorus session`), so
|
|
189
|
+
* prose that merely mentions "Chorus session" does not suppress injection.
|
|
190
|
+
* Covers both this extension's injected block and any main-agent template.
|
|
191
|
+
* The header is exactly "--- Chorus session" followed by " (…)" or end of line;
|
|
192
|
+
* a hyphenated continuation like "--- Chorus session-notes" is not a header.
|
|
190
193
|
*/
|
|
191
|
-
export
|
|
192
|
-
|
|
193
|
-
reason: string;
|
|
194
|
-
optout: boolean;
|
|
195
|
-
hint: string;
|
|
194
|
+
export function hasSessionMarker(task: string): boolean {
|
|
195
|
+
return /^--- Chorus session(?=[ (\u2014]|$)/m.test(task);
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
/**
|
|
199
|
-
* Detect
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
* (3) the `openspec` CLI is on PATH
|
|
199
|
+
* Detect an async (detached) nicobailon pi-subagents `subagent` run from its
|
|
200
|
+
* tool_result EVENT, and extract the run id that its completion events will
|
|
201
|
+
* carry.
|
|
203
202
|
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
203
|
+
* The official bundled subagent (blocking) returns at tool_result with no run
|
|
204
|
+
* id — the session lifecycle closes there. The nicobailon `pi-subagents` tool
|
|
205
|
+
* launches async (detached) by default: spawn returns immediately with
|
|
206
|
+
* `details.asyncId`, and completion arrives later on the pi event bus as
|
|
207
|
+
* `subagent:async-complete` / `subagent:process-terminal` with `{runId}`/`{id}`.
|
|
208
|
+
* Only `details.asyncId`/`details.runId` are trusted — the nicobailon contract
|
|
209
|
+
* always carries the run id in `details` for async launches (verified against
|
|
210
|
+
* pi-subagents src/runs/foreground/subagent-executor.ts: async started returns
|
|
211
|
+
* `details: { mode, results, asyncId, asyncDir }` (:1584/:1375), and nicobailon
|
|
212
|
+
* itself reads `result.details.asyncId` (:1711/:1967/:2119); `id`/`prefix`
|
|
213
|
+
* appear only in completion-event payloads, never in tool_result details), and
|
|
214
|
+
* a blocking run's worker output (even standalone JSON) must never be
|
|
215
|
+
* misclassified as an async run (which would leak the session until
|
|
216
|
+
* session_shutdown).
|
|
206
217
|
*/
|
|
207
|
-
export function
|
|
208
|
-
|
|
209
|
-
|
|
218
|
+
export function extractRunIdFromToolResultEvent(event: {
|
|
219
|
+
details?: unknown;
|
|
220
|
+
}): string | null {
|
|
221
|
+
const d = (event.details ?? {}) as Record<string, unknown>;
|
|
222
|
+
for (const key of ["asyncId", "runId"]) {
|
|
223
|
+
if (typeof d[key] === "string" && (d[key] as string).length > 0) return d[key] as string;
|
|
224
|
+
}
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* The resolved spec mode surfaced to the agent. `openspec` = the OpenSpec
|
|
230
|
+
* (openspec-aware) path; `lite` = Chorus-native lightweight specs
|
|
231
|
+
* (`.chorus/specs/<slug>/`); `off` = free-form, no spec artifact.
|
|
232
|
+
*/
|
|
233
|
+
export type SpecMode = "lite" | "openspec" | "off";
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Inputs to the spec-mode resolver (env values + repo root). Mirrors the
|
|
237
|
+
* canonical bash resolver `public/chorus-plugin/bin/resolve-spec-mode.sh`.
|
|
238
|
+
*/
|
|
239
|
+
export interface SpecModeInputs {
|
|
240
|
+
/** CHORUS_SPEC_MODE — explicit override: "lite" | "openspec" | "off" (else unset/""). */
|
|
241
|
+
specMode?: string;
|
|
242
|
+
/** CHORUS_OPENSPEC_MODE — legacy opt-out: "off" disables OpenSpec. */
|
|
243
|
+
openspecMode?: string;
|
|
244
|
+
/** CLAUDE_PLUGIN_OPTION_ENABLEOPENSPEC — plugin toggle: "false" disables OpenSpec (default "true"). */
|
|
245
|
+
enableOpenSpec?: string;
|
|
246
|
+
/** Repo root to probe for openspec/. */
|
|
247
|
+
projectRoot: string;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Resolved spec mode for a repo — the TS mirror of the bash resolver's output
|
|
252
|
+
* vars. `specFail` non-empty ⇒ a stage skill MUST halt (an explicit
|
|
253
|
+
* `CHORUS_SPEC_MODE=openspec` that cannot be honored); `chorusOpenspecActive`
|
|
254
|
+
* is true only when the resolved mode is a USABLE openspec.
|
|
255
|
+
*/
|
|
256
|
+
export interface SpecModeResult {
|
|
257
|
+
specMode: SpecMode;
|
|
258
|
+
specReason: string;
|
|
259
|
+
specFail: string;
|
|
260
|
+
openspecUsable: boolean;
|
|
261
|
+
openspecUsableReason: string;
|
|
262
|
+
openspecHint: string;
|
|
263
|
+
chorusOpenspecActive: boolean;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Resolve the active Chorus spec mode for a repo — the TypeScript reimplementation
|
|
268
|
+
* of `public/chorus-plugin/bin/resolve-spec-mode.sh` (which the bash ports copy
|
|
269
|
+
* byte-identically; the TS ports reimplement + ship a same-contract test). Pure
|
|
270
|
+
* given injectable fs + execSync.
|
|
271
|
+
*
|
|
272
|
+
* Rule (per owner): an explicit `CHORUS_SPEC_MODE` wins; when unset, OpenSpec stays
|
|
273
|
+
* the default whenever it is usable (openspec/ dir + CLI, not disabled), and lite
|
|
274
|
+
* is the fallback only when OpenSpec is absent or disabled. An explicit
|
|
275
|
+
* `=openspec` that isn't usable fails fast (`specFail`).
|
|
276
|
+
*/
|
|
277
|
+
export function resolveSpecMode(
|
|
278
|
+
inputs: SpecModeInputs,
|
|
210
279
|
fs: FsLike,
|
|
211
280
|
execSync: ExecSync,
|
|
212
|
-
):
|
|
213
|
-
|
|
214
|
-
|
|
281
|
+
): SpecModeResult {
|
|
282
|
+
const projectRoot = inputs.projectRoot || "";
|
|
283
|
+
|
|
284
|
+
// --- Is OpenSpec usable? (needs openspec/ dir + CLI on PATH + not disabled) ---
|
|
285
|
+
// enableOpenSpec toggle is checked BEFORE the legacy CHORUS_OPENSPEC_MODE, so a
|
|
286
|
+
// plugin-level opt-out wins the reason string (matches the bash resolver order).
|
|
287
|
+
let openspecDisabled = false;
|
|
288
|
+
let disabledReason = "";
|
|
289
|
+
if ((inputs.enableOpenSpec ?? "true") !== "true") {
|
|
290
|
+
openspecDisabled = true;
|
|
291
|
+
disabledReason = "enableOpenSpec userConfig=false (plugin-level opt-out)";
|
|
292
|
+
} else if (inputs.openspecMode === "off") {
|
|
293
|
+
openspecDisabled = true;
|
|
294
|
+
disabledReason = "CHORUS_OPENSPEC_MODE=off (legacy opt-out)";
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
let openspecUsable = false;
|
|
298
|
+
let openspecUsableReason = "";
|
|
299
|
+
let openspecHint = "";
|
|
300
|
+
if (openspecDisabled) {
|
|
301
|
+
openspecUsableReason = disabledReason;
|
|
302
|
+
} else if (!fs.existsSync(`${projectRoot}/openspec`)) {
|
|
303
|
+
openspecUsableReason = `no openspec/ directory at ${projectRoot}/openspec`;
|
|
304
|
+
openspecHint = "npm i -g @fission-ai/openspec && openspec init";
|
|
305
|
+
} else if (!openspecCliPresent(execSync)) {
|
|
306
|
+
openspecUsableReason = "openspec/ directory present but `openspec` CLI not on PATH";
|
|
307
|
+
openspecHint = "npm i -g @fission-ai/openspec";
|
|
308
|
+
} else {
|
|
309
|
+
openspecUsable = true;
|
|
310
|
+
openspecUsableReason = "openspec/ directory + openspec CLI both present";
|
|
215
311
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
312
|
+
|
|
313
|
+
// --- Resolve CHORUS_SPEC_MODE (unset and "" are treated the same, as in bash) ---
|
|
314
|
+
let specMode: SpecMode;
|
|
315
|
+
let specReason: string;
|
|
316
|
+
let specFail = "";
|
|
317
|
+
const raw = inputs.specMode ?? "";
|
|
318
|
+
switch (raw) {
|
|
319
|
+
case "lite":
|
|
320
|
+
specMode = "lite";
|
|
321
|
+
specReason = "explicit — Chorus-native lightweight specs in .chorus/specs/<slug>/";
|
|
322
|
+
break;
|
|
323
|
+
case "off":
|
|
324
|
+
specMode = "off";
|
|
325
|
+
specReason = "explicit — free-form, no spec artifact";
|
|
326
|
+
break;
|
|
327
|
+
case "openspec":
|
|
328
|
+
specMode = "openspec";
|
|
329
|
+
if (openspecUsable) {
|
|
330
|
+
specReason = `explicit; ${openspecUsableReason}`;
|
|
331
|
+
} else if (openspecDisabled) {
|
|
332
|
+
specReason = `explicit, but OpenSpec is disabled: ${openspecUsableReason}`;
|
|
333
|
+
specFail = `config conflict — CHORUS_SPEC_MODE=openspec vs OpenSpec disabled (${openspecUsableReason}); re-enable OpenSpec or set CHORUS_SPEC_MODE=lite`;
|
|
334
|
+
} else {
|
|
335
|
+
specReason = `explicit, but OpenSpec is not installed: ${openspecUsableReason}`;
|
|
336
|
+
specFail = `OpenSpec not usable (${openspecUsableReason})`;
|
|
337
|
+
}
|
|
338
|
+
break;
|
|
339
|
+
case "":
|
|
340
|
+
// Unset: OpenSpec is the default when usable; lite is the fallback otherwise.
|
|
341
|
+
if (openspecUsable) {
|
|
342
|
+
specMode = "openspec";
|
|
343
|
+
specReason = `default — ${openspecUsableReason}; set CHORUS_SPEC_MODE=lite for Chorus-native specs, =off to disable`;
|
|
344
|
+
} else {
|
|
345
|
+
specMode = "lite";
|
|
346
|
+
specReason = `default — OpenSpec not usable (${openspecUsableReason}); using Chorus-native lightweight specs in .chorus/specs/<slug>/`;
|
|
347
|
+
}
|
|
348
|
+
break;
|
|
349
|
+
default:
|
|
350
|
+
// Unrecognized value: treat like unset (OpenSpec-if-usable, else lite).
|
|
351
|
+
if (openspecUsable) {
|
|
352
|
+
specMode = "openspec";
|
|
353
|
+
specReason = `CHORUS_SPEC_MODE='${raw}' unrecognized; falling back to default (${openspecUsableReason})`;
|
|
354
|
+
} else {
|
|
355
|
+
specMode = "lite";
|
|
356
|
+
specReason = `CHORUS_SPEC_MODE='${raw}' unrecognized; OpenSpec not usable, defaulting to lite`;
|
|
357
|
+
}
|
|
219
358
|
}
|
|
220
|
-
|
|
359
|
+
|
|
360
|
+
const chorusOpenspecActive = specMode === "openspec" && specFail === "";
|
|
361
|
+
return {
|
|
362
|
+
specMode,
|
|
363
|
+
specReason,
|
|
364
|
+
specFail,
|
|
365
|
+
openspecUsable,
|
|
366
|
+
openspecUsableReason,
|
|
367
|
+
openspecHint,
|
|
368
|
+
chorusOpenspecActive,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function openspecCliPresent(execSync: ExecSync): boolean {
|
|
221
373
|
try {
|
|
222
374
|
execSync("command -v openspec", { stdio: "ignore" });
|
|
223
|
-
|
|
375
|
+
return true;
|
|
224
376
|
} catch {
|
|
225
|
-
|
|
377
|
+
return false;
|
|
226
378
|
}
|
|
227
|
-
if (!cliPresent) {
|
|
228
|
-
return {
|
|
229
|
-
active: false,
|
|
230
|
-
reason: "openspec/ directory present but `openspec` CLI not on PATH",
|
|
231
|
-
optout: false,
|
|
232
|
-
hint: "install with: npm i -g @fission-ai/openspec",
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
return { active: true, reason: "openspec/ directory + openspec CLI both present", optout: false, hint: "" };
|
|
236
379
|
}
|
|
237
380
|
|
|
238
381
|
/**
|
|
239
382
|
* Build the user-visible one-line startup banner (the Pi equivalent of the
|
|
240
|
-
* Claude plugin's SessionStart `systemMessage` / Codex `$chorus` toast).
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
* -
|
|
244
|
-
* -
|
|
245
|
-
* -
|
|
383
|
+
* Claude plugin's SessionStart `systemMessage` / Codex `$chorus` toast). The
|
|
384
|
+
* suffix reflects the resolved spec mode:
|
|
385
|
+
* - openspec (usable) -> "(spec: OpenSpec)"
|
|
386
|
+
* - lite -> "(spec: spec-lite)"
|
|
387
|
+
* - off -> "(spec: off — free-form)"
|
|
388
|
+
* - openspec requested but unusable (specFail) -> warning, "(spec: OpenSpec unusable — …)"
|
|
246
389
|
*
|
|
247
|
-
* Plus two non-
|
|
390
|
+
* Plus two non-connected states:
|
|
248
391
|
* - not configured -> warning that CHORUS_URL / CHORUS_API_KEY are missing
|
|
249
392
|
* - connection failed -> error that the checkin couldn't reach Chorus
|
|
250
393
|
*
|
|
@@ -259,7 +402,7 @@ export function buildSessionBanner(args: {
|
|
|
259
402
|
configured: boolean;
|
|
260
403
|
connected: boolean;
|
|
261
404
|
chorusUrl: string;
|
|
262
|
-
|
|
405
|
+
spec: SpecModeResult;
|
|
263
406
|
}): SessionBanner {
|
|
264
407
|
// Not configured at all — env vars missing. Warn once so the user knows
|
|
265
408
|
// the plugin loaded but is inert (Claude's hook emits the same warning).
|
|
@@ -278,18 +421,23 @@ export function buildSessionBanner(args: {
|
|
|
278
421
|
};
|
|
279
422
|
}
|
|
280
423
|
|
|
281
|
-
// Connected. Append the
|
|
424
|
+
// Connected. Append the resolved spec-mode suffix.
|
|
425
|
+
const spec = args.spec;
|
|
282
426
|
let suffix: string;
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
427
|
+
let level: "info" | "warning" = "info";
|
|
428
|
+
if (spec.specFail) {
|
|
429
|
+
suffix = `(spec: OpenSpec requested but unusable — ${spec.openspecUsableReason}; spec authoring will halt)`;
|
|
430
|
+
level = "warning";
|
|
431
|
+
} else if (spec.specMode === "openspec") {
|
|
432
|
+
suffix = "(spec: OpenSpec)";
|
|
433
|
+
} else if (spec.specMode === "lite") {
|
|
434
|
+
suffix = "(spec: spec-lite)";
|
|
287
435
|
} else {
|
|
288
|
-
suffix = "(
|
|
436
|
+
suffix = "(spec: off — free-form)";
|
|
289
437
|
}
|
|
290
438
|
return {
|
|
291
439
|
message: `Chorus connected at ${args.chorusUrl} ${suffix}`,
|
|
292
|
-
level
|
|
440
|
+
level,
|
|
293
441
|
};
|
|
294
442
|
}
|
|
295
443
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chorus-aidlc/chorus-pi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Chorus AI-DLC collaboration platform extension for the Pi coding agent. Provides skills for every stage of the AI-DLC lifecycle, read-only reviewer subagents, and session-aware extension hooks. The Chorus MCP server is auto-discovered from the repo's .mcp.json by pi-mcp-adapter — no installer required.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Chorus-AIDLC"
|
|
@@ -52,7 +52,10 @@
|
|
|
52
52
|
},
|
|
53
53
|
"pi": {
|
|
54
54
|
"extensions": ["./extensions"],
|
|
55
|
-
"skills": ["./skills"]
|
|
55
|
+
"skills": ["./skills"],
|
|
56
|
+
"subagents": {
|
|
57
|
+
"agents": ["./agents"]
|
|
58
|
+
}
|
|
56
59
|
},
|
|
57
60
|
"//note": "Reviewer subagents use pi's official subagent pattern, bundled at extensions/subagent/ (index.ts + agents.ts, copied from earendil-works/pi's examples). The copied agents.ts discovers this package's own agents/*.md via a package-relative BUNDLED_DIR, so the 3 reviewer agents load with ZERO manual copy into ~/.pi/agent/agents/. pi auto-loads extensions/subagent/index.ts as a subdirectory-with-index extension (no manifest entry needed)."
|
|
58
61
|
}
|
package/skills/chorus/SKILL.md
CHANGED
|
@@ -4,7 +4,7 @@ description: Chorus AI Agent collaboration platform — overview, common tools,
|
|
|
4
4
|
license: AGPL-3.0
|
|
5
5
|
metadata:
|
|
6
6
|
author: chorus
|
|
7
|
-
version: "0.
|
|
7
|
+
version: "0.18.0"
|
|
8
8
|
category: project-management
|
|
9
9
|
mcp_server: chorus
|
|
10
10
|
---
|
|
@@ -350,20 +350,22 @@ export CHORUS_MAX_CODE_REVIEW_ROUNDS=5 # 0 = unlimited
|
|
|
350
350
|
|
|
351
351
|
When enabled, reviewers run as read-only sub-agents and post a VERDICT comment on the proposal/task/idea. Three possible outcomes: **PASS** (no issues), **PASS WITH NOTES** (minor non-blocking notes), or **FAIL** (BLOCKERs found). Results are advisory — they do not block approval, verification, or ship; the code-review gateway in particular is behavioral (it does not change the Idea's stored status). On a code-review FAIL, fix it via the `/skill:quick-dev` workflow: `chorus_create_tasks` with `proposalUuid` set to the current approved proposal so the fix tasks attach to it. Group related small BLOCKERs into one cohesive task by default; split only materially large or independently testable fixes. Each fix task must self-check its acceptance criteria and pass independent task review plus admin verification. Re-run the gateway only after every fix task is successfully `done`; if there is a failed or cancelled fix task, stop and escalate instead. Disabling reduces token usage but removes the independent quality gate.
|
|
352
352
|
|
|
353
|
-
### 6.
|
|
353
|
+
### 6. Spec mode: OpenSpec (default when usable) vs spec-lite (fallback)
|
|
354
354
|
|
|
355
|
-
|
|
355
|
+
The extension's `session_start` handler resolves one **spec mode** per session (via the TS `resolveSpecMode`, the single source of truth) and injects a `## Spec Mode` section stating it — the stage skills **consume** that value, they don't re-derive it. Resolution: an explicit `CHORUS_SPEC_MODE` (`lite`/`openspec`/`off`) wins; when unset, **OpenSpec is the default whenever it is usable** (`CHORUS_OPENSPEC_MODE` ≠ `off`, an `openspec/` directory at the project root, and the `openspec` CLI on `PATH`). When OpenSpec is absent or disabled, the mode falls back to **spec-lite** — a Chorus-native, git-tracked model with a durable local `.chorus/specs/<slug>/spec.md` per capability (never synced) plus dated per-change folders `<slug>/<YYYY-MM-DD>-<change-slug>/` of Chorus-typed docs mirrored 1:1 into Chorus (see `/skill:spec-lite`). `CHORUS_SPEC_MODE=off` selects free-form (no spec artifact).
|
|
356
356
|
|
|
357
|
-
|
|
357
|
+
OpenSpec spec-driven path: `/skill:proposal`, `/skill:develop`, and `/skill:yolo` write `proposal.md` / `design.md` / spec deltas on disk and mirror them into Chorus drafts.
|
|
358
|
+
|
|
359
|
+
**When the user wants OpenSpec on** (e.g. they saw `(spec: spec-lite)` / `(spec: off …)` in the banner), actually **enable it for them** — run whichever steps are missing, don't just describe them:
|
|
358
360
|
|
|
359
361
|
```bash
|
|
360
362
|
npm i -g @fission-ai/openspec # 1. install the CLI if it's not on PATH (global, pure Node)
|
|
361
363
|
openspec init # 2. scaffold openspec/ (interactive; pick your editor tooling)
|
|
362
364
|
```
|
|
363
365
|
|
|
364
|
-
The
|
|
366
|
+
The spec mode is resolved **once at session start**, so it can't flip mid-session — after the steps succeed, tell the user to **restart the session**; the `## Spec Mode` section then reads `CHORUS_SPEC_MODE=openspec (…)` and the stage skills fold in the `openspec-aware` skill automatically.
|
|
365
367
|
|
|
366
|
-
To turn
|
|
368
|
+
To turn OpenSpec off, set `CHORUS_OPENSPEC_MODE=off` — the mode then falls back to **spec-lite** (or set `CHORUS_SPEC_MODE=off` for free-form). The `## Spec Mode` section always states the resolved mode + reason.
|
|
367
369
|
|
|
368
370
|
---
|
|
369
371
|
|
|
@@ -426,7 +428,8 @@ This is the core overview skill. For stage-specific workflows, use:
|
|
|
426
428
|
| **Development** | `/skill:develop` | Claim Tasks, report work, session & parallel sub-agent integration |
|
|
427
429
|
| **Review** | `/skill:review` | Approve/reject Proposals, verify Tasks, project governance |
|
|
428
430
|
| **Docs** | `/skill:docs` | Consult the live Chorus documentation site to answer product-usage questions — UI workflow, agent/plugin setup, API/MCP, deployment, operations |
|
|
429
|
-
| **OpenSpec mode** | `openspec-aware` |
|
|
431
|
+
| **OpenSpec mode** | `openspec-aware` | **Shared sub-procedure** invoked by `/skill:proposal`, `/skill:develop`, and `/skill:yolo` when the resolved spec mode is a usable OpenSpec (the default when `openspec/` + CLI present and not disabled). Scaffolds `openspec/changes/<slug>/` on disk and mirrors files into Chorus document drafts via `chorus mcp call --arg-file` (`chorus-mcp-call.sh` wrapper as fallback). See `skills/openspec-aware/SKILL.md`. |
|
|
432
|
+
| **spec-lite mode** | `spec-lite` | **Shared sub-procedure** and the fallback when OpenSpec isn't usable (or `CHORUS_SPEC_MODE=lite`). Durable local `.chorus/specs/<slug>/spec.md` (never synced) + dated per-change folders of Chorus-typed docs mirrored 1:1 into Chorus via `--arg-file`. No CLI/validation. See `skills/spec-lite/SKILL.md`. |
|
|
430
433
|
|
|
431
434
|
### Getting Started
|
|
432
435
|
|
package/skills/develop/SKILL.md
CHANGED
|
@@ -4,7 +4,7 @@ description: Chorus Development workflow — claim tasks, report work, manage se
|
|
|
4
4
|
license: AGPL-3.0
|
|
5
5
|
metadata:
|
|
6
6
|
author: chorus
|
|
7
|
-
version: "0.
|
|
7
|
+
version: "0.18.0"
|
|
8
8
|
category: project-management
|
|
9
9
|
mcp_server: chorus
|
|
10
10
|
---
|
|
@@ -140,7 +140,9 @@ Each task and proposal includes a `commentCount` field — use it to decide whic
|
|
|
140
140
|
>
|
|
141
141
|
> When the LAST task of an OpenSpec idea is verified, the extension injects an archive reminder (`openspec-aware` §3.9) — run `openspec archive <slug> --yes`, then mirror each emitted `openspec/specs/<capability>/spec.md` back via §3.8.
|
|
142
142
|
>
|
|
143
|
-
>
|
|
143
|
+
> **Document update flow (spec-lite mode):** if the proposal `description` contains a line `Spec-lite: .chorus/specs/<slug>/<YYYY-MM-DD>-<change-slug>/`, the project's PRD / tech_design / … Documents are **mirrors** of the files in that dated folder. To update such a Document, load the `spec-lite` skill (`/skill:spec-lite`) and follow its Mirror section: edit the local `<type>.md` file first, then mirror it via `chorus mcp call chorus_pm_update_document "{\"documentUuid\":\"<uuid>\"}" --arg-file content=<file>` (recorded `documentUuid` from the file's frontmatter), falling back to the `chorus-mcp-call.sh` wrapper when `chorus` is not on `PATH`, `chorus_check_response` halting on error. Same **⛔ do-not-hand-type-`content`** rule as OpenSpec. The durable `.chorus/specs/<slug>/spec.md` is edited in place too but is **never mirrored** (git history is its record). No archive flow — spec-lite has no CLI/validate/archive; on delivery just set `spec.md` `status: done`.
|
|
144
|
+
>
|
|
145
|
+
> In the no-OpenSpec, no-spec-lite fallback (free-form: no locator line), edit the Document content directly via the existing MCP tool with no wrapper, no local file step.
|
|
144
146
|
|
|
145
147
|
### Step 5: Start Working
|
|
146
148
|
|
|
@@ -229,13 +231,13 @@ After the reviewer completes, read its VERDICT:
|
|
|
229
231
|
```
|
|
230
232
|
chorus_get_comments({ targetType: "task", targetUuid: "<task-uuid>" })
|
|
231
233
|
```
|
|
232
|
-
Find
|
|
234
|
+
Find THIS round's `VERDICT:` comment — the one posted after your dispatch, not an older round's — and act on it:
|
|
233
235
|
|
|
234
236
|
- **VERDICT: PASS** — All AC verified, no issues. Proceed to admin verification.
|
|
235
237
|
- **VERDICT: PASS WITH NOTES** — All AC verified, minor notes. Proceed to admin verification (notes are non-blocking).
|
|
236
238
|
- **VERDICT: FAIL** — BLOCKERs found. Do NOT verify. Fix the BLOCKERs listed in the reviewer's comment, then resubmit.
|
|
237
239
|
|
|
238
|
-
If no new `VERDICT:` comment appears after the reviewer returns, it
|
|
240
|
+
If no new `VERDICT:` comment appears after the reviewer returns, check what it *did* post. A comment reporting that the round limit was reached, or any other explicit refusal to review, is a deliberate escalation to a human: STOP — do not respawn, do not self-review, do not post a VERDICT of your own. If it posted nothing at all, respawn it ONCE, telling it to stay within its turn budget and reserve its last turns for the VERDICT, then apply this same check again to what the retry posts. An explicit refusal from the retry still means STOP; only a second true silence lets you review the task yourself as a read-only pass using the checklist and POST the VERDICT comment. **Absence is never a PASS.**
|
|
239
241
|
|
|
240
242
|
> **Final code-review gateway (after the Idea's LAST task is verified):** when the task you just verified is the **last** task of its idea-rooted proposal, the feature is about to ship — the extension nudges you to spawn `chorus-code-reviewer` (gated by `CHORUS_ENABLE_CODE_REVIEWER`, default on). Spawn it yourself via the blocking `subagent` tool, passing the `ideaUuid` + round number; it reviews the Idea's **aggregate** code change across all its tasks (cross-task integration, architecture, security, regression, feature-level coverage) and posts one `VERDICT` comment on the **idea**. `PASS` / `PASS WITH NOTES` → ship; `FAIL` → fix via `/skill:quick-dev` (`chorus_create_tasks` with `proposalUuid` set to the current approved proposal so the fix tasks attach to it — do NOT reopen the verified tasks). Group related small BLOCKERs by default; split only materially large or independently testable fixes. Require AC self-check, independent task review, and admin verification for every fix task. Re-run aggregate review only after every fix is successfully `done`; a failed or cancelled fix stops the loop and escalates, bounded by `CHORUS_MAX_CODE_REVIEW_ROUNDS` (env, default 3; 0 = unlimited). Advisory/behavioral, like the other reviewers. Run it **before** any idea-completion report.
|
|
241
243
|
|
package/skills/docs/SKILL.md
CHANGED
package/skills/idea/SKILL.md
CHANGED
|
@@ -1,75 +1,67 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: openspec-aware
|
|
3
|
-
description:
|
|
3
|
+
description: OpenSpec-mode authoring for Chorus PM workflows in Pi. The default whenever OpenSpec is usable; consumes the resolved `## Spec Mode` (never re-detects). Scaffolds `openspec/changes/<slug>/` on disk, and mirrors Markdown files into Chorus document drafts via `chorus mcp call --arg-file` (bash `chorus-mcp-call.sh` wrapper as fallback). Required reading for the proposal, develop, and yolo skills. When OpenSpec is not the resolved mode, this skill no-ops and the caller follows the resolved mode (spec-lite or free-form).
|
|
4
4
|
license: AGPL-3.0
|
|
5
5
|
metadata:
|
|
6
6
|
author: chorus
|
|
7
|
-
version: "0.
|
|
7
|
+
version: "0.18.0"
|
|
8
8
|
category: project-management
|
|
9
9
|
mcp_server: chorus
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
# OpenSpec-aware Authoring (Pi plugin)
|
|
13
13
|
|
|
14
|
-
This skill is a **shared sub-procedure** invoked by the Chorus stage skills (proposal, develop, yolo)
|
|
14
|
+
This skill is a **shared sub-procedure** invoked by the Chorus stage skills (proposal, develop, yolo) when the resolved spec mode is a **usable OpenSpec** — spec-driven authoring through the [OpenSpec CLI](https://github.com/Fission-AI/OpenSpec):
|
|
15
15
|
|
|
16
|
-
- Activates when **
|
|
17
|
-
- Otherwise the calling skill
|
|
16
|
+
- Activates when the resolved spec mode is a **usable OpenSpec** (see §1): `CHORUS_SPEC_MODE=openspec` *or* unset, **and** `CHORUS_OPENSPEC_MODE` not `off`, an `openspec/` directory exists at the project root, and the `openspec` CLI is on `PATH`.
|
|
17
|
+
- Otherwise the calling skill follows the resolved mode — **spec-lite** (the default when OpenSpec isn't usable) or free-form (`CHORUS_SPEC_MODE=off`).
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
> **See also — `spec-lite` (the lightweight fallback):** OpenSpec (this skill) stays the default whenever usable. When OpenSpec is absent or disabled — or `CHORUS_SPEC_MODE=lite` — the mode resolves to **spec-lite**: a durable local `.chorus/specs/<slug>/spec.md` (never synced) + per-change dated folders `<slug>/<YYYY-MM-DD>-<change-slug>/` of Chorus-typed docs mirrored 1:1 into Chorus via the same `--arg-file` transport. See `/skill:spec-lite`.
|
|
20
|
+
|
|
21
|
+
When you reach a point in proposal / develop / yolo where this skill is referenced, **read the resolved mode from the `## Spec Mode` section** (see §1) and branch on it. Do not re-derive the detection — the `session_start` handler has already resolved it once for this session.
|
|
20
22
|
|
|
21
23
|
---
|
|
22
24
|
|
|
23
|
-
## §1.
|
|
25
|
+
## §1. Spec mode — already resolved at session_start
|
|
24
26
|
|
|
25
|
-
The
|
|
27
|
+
The chorus-pi extension's `session_start` handler resolves the spec mode once when the session opens (via the TS `resolveSpecMode`, the single source of truth — the reimplementation of the canonical bash resolver) and writes a `## Spec Mode` section into the injected context stating `CHORUS_SPEC_MODE=<lite|openspec|off>`; when the resolved mode is a usable OpenSpec it also carries a `CHORUS_OPENSPEC_ACTIVE=1` line. That line is present only when `CHORUS_SPEC_MODE` is `openspec` **or unset**, **and all three** of these hold:
|
|
26
28
|
|
|
27
|
-
1.
|
|
29
|
+
1. OpenSpec is not disabled (`CHORUS_OPENSPEC_MODE` not `off`, `enableOpenSpec` not `false`).
|
|
28
30
|
2. The project root contains an `openspec/` directory (i.e. someone ran `openspec init` here).
|
|
29
31
|
3. The `openspec` CLI is on `PATH`.
|
|
30
32
|
|
|
31
|
-
Both signals (2) and (3) are required because the OpenSpec authoring path needs the working directory **and** the CLI
|
|
33
|
+
Both signals (2) and (3) are required because the OpenSpec authoring path needs the working directory **and** the CLI. If signal (2) holds but (3) does not, the `## Spec Mode` section carries an install hint (`npm i -g @fission-ai/openspec`); pass it through if asked.
|
|
32
34
|
|
|
33
35
|
### How to read the value
|
|
34
36
|
|
|
35
|
-
You should already see something like this in your context (look for the `##
|
|
37
|
+
You should already see something like this in your context (look for the `## Spec Mode` section near the top of the conversation):
|
|
36
38
|
|
|
37
39
|
```
|
|
38
|
-
##
|
|
40
|
+
## Spec Mode
|
|
41
|
+
|
|
42
|
+
CHORUS_SPEC_MODE=openspec (default — openspec/ directory + openspec CLI both present)
|
|
39
43
|
|
|
40
44
|
CHORUS_OPENSPEC_ACTIVE=1 (openspec/ directory + openspec CLI both present)
|
|
41
45
|
```
|
|
42
46
|
|
|
43
|
-
or:
|
|
47
|
+
or (resolved to lite / off — no `CHORUS_OPENSPEC_ACTIVE=1` line):
|
|
44
48
|
|
|
45
49
|
```
|
|
46
|
-
##
|
|
50
|
+
## Spec Mode
|
|
47
51
|
|
|
48
|
-
|
|
52
|
+
CHORUS_SPEC_MODE=lite (default — OpenSpec not usable: no openspec/ directory at /path/to/repo/openspec)
|
|
49
53
|
```
|
|
50
54
|
|
|
51
55
|
Branch:
|
|
52
56
|
|
|
53
|
-
- `CHORUS_OPENSPEC_ACTIVE=1` → follow §3 (OpenSpec authoring).
|
|
54
|
-
- `CHORUS_OPENSPEC_ACTIVE=
|
|
55
|
-
|
|
56
|
-
### Manual fallback
|
|
57
|
+
- `CHORUS_OPENSPEC_ACTIVE=1` line present → follow §3 (OpenSpec authoring).
|
|
58
|
+
- No `CHORUS_OPENSPEC_ACTIVE=1` line → this skill is a no-op; return to the caller, which follows the resolved `CHORUS_SPEC_MODE` (**spec-lite** or free-form). **Do not** scaffold `openspec/changes/`. **Do not** add the slug line to the proposal description.
|
|
57
59
|
|
|
58
|
-
If
|
|
60
|
+
If the `## Spec Mode` section shows an explicit `CHORUS_SPEC_MODE=openspec` that **cannot be honored** (OpenSpec disabled or not installed — it carries a config-conflict / not-installed reason and no `CHORUS_OPENSPEC_ACTIVE=1` line), the caller MUST **halt** and surface it — do not silently fall back.
|
|
59
61
|
|
|
60
|
-
|
|
61
|
-
if [ "${CHORUS_OPENSPEC_MODE:-}" = "off" ]; then
|
|
62
|
-
CHORUS_OPENSPEC_ACTIVE=0
|
|
63
|
-
elif [ ! -d "$PWD/openspec" ]; then
|
|
64
|
-
CHORUS_OPENSPEC_ACTIVE=0
|
|
65
|
-
elif ! openspec --version >/dev/null 2>&1; then
|
|
66
|
-
CHORUS_OPENSPEC_ACTIVE=0
|
|
67
|
-
else
|
|
68
|
-
CHORUS_OPENSPEC_ACTIVE=1
|
|
69
|
-
fi
|
|
70
|
-
```
|
|
62
|
+
### Manual fallback
|
|
71
63
|
|
|
72
|
-
|
|
64
|
+
The mode is resolved by the extension in TypeScript (`resolveSpecMode`) at `session_start` — there is no separate bash resolver to source, and you must **not** hand-roll the rule (a hand-rolled OpenSpec-only check ignores `CHORUS_SPEC_MODE` and the lite/off cases). If the `## Spec Mode` section is genuinely absent (e.g. the checkin/connection failed, or you are a forwarded sub-agent whose parent context was not carried), set `CHORUS_SPEC_MODE` explicitly (`lite` | `openspec` | `off`) and relaunch so the extension re-resolves it — rather than guessing.
|
|
73
65
|
|
|
74
66
|
---
|
|
75
67
|
|
|
@@ -393,15 +385,15 @@ The hook is read-only; you (the agent) perform the archive:
|
|
|
393
385
|
|
|
394
386
|
---
|
|
395
387
|
|
|
396
|
-
## §4.
|
|
388
|
+
## §4. When OpenSpec is not the resolved mode
|
|
397
389
|
|
|
398
|
-
When
|
|
390
|
+
When the resolved mode is not a usable OpenSpec (no `CHORUS_OPENSPEC_ACTIVE=1` line), this skill is a **no-op** — return to the calling skill, which follows the resolved `CHORUS_SPEC_MODE`: **spec-lite** (the default when OpenSpec isn't usable) or free-form (`=off`). From this skill's side:
|
|
399
391
|
|
|
400
392
|
- No `openspec/changes/` folder is created or referenced.
|
|
401
393
|
- No `OpenSpec change slug: …` line is added to the proposal description.
|
|
402
|
-
-
|
|
403
|
-
- Rule 1 (wrapper-only mirror)
|
|
404
|
-
- The §3.9 archive hook does nothing (no slug → silent exit).
|
|
394
|
+
- In **spec-lite**, the caller follows `/skill:spec-lite` (durable `.chorus/specs/<slug>/spec.md` + dated per-change folders mirrored via `--arg-file`); in **free-form**, document drafts are authored via direct MCP `chorus_pm_add_document_draft` calls with inline `content` — same as before this skill existed.
|
|
395
|
+
- Rule 1 (wrapper-only mirror) still applies in spec-lite (mirror from the local file); in free-form there is no local file source of truth.
|
|
396
|
+
- The §3.9 archive hook does nothing (no slug → silent exit); spec-lite has no archive step at all.
|
|
405
397
|
|
|
406
398
|
---
|
|
407
399
|
|
|
@@ -485,8 +477,8 @@ This is project-wide policy: no silent errors.
|
|
|
485
477
|
|
|
486
478
|
When invoked from a stage skill (proposal / develop / yolo):
|
|
487
479
|
|
|
488
|
-
1. Read
|
|
489
|
-
2.
|
|
480
|
+
1. Read the `## Spec Mode` section in the session_start context (§1) — proceed only if it carries the `CHORUS_OPENSPEC_ACTIVE=1` line. If the section is absent, use the fallback in §1 (set `CHORUS_SPEC_MODE` explicitly + relaunch).
|
|
481
|
+
2. No `CHORUS_OPENSPEC_ACTIVE=1` line → no-op; return to the caller per the resolved `CHORUS_SPEC_MODE` (spec-lite or free-form) — see §4.
|
|
490
482
|
3. Otherwise:
|
|
491
483
|
a. Pick `$SLUG` (§3.1).
|
|
492
484
|
b. `openspec new change "$SLUG"` (§3.2).
|