@mstar-harness/opencode 3.1.2 → 3.1.3
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/CHANGELOG.md +14 -0
- package/dist/mstar.js +195 -108
- package/harness-commands/iteration-drive.md +9 -90
- package/harness-commands/iteration-loop.md +22 -62
- package/harness-commands/iteration-start.md +20 -77
- package/harness-skills/mstar-audit/SKILL.md +2 -2
- package/harness-skills/mstar-compound-refresh/SKILL.md +1 -1
- package/harness-skills/mstar-dispatch-gates/SKILL.md +1 -1
- package/harness-skills/mstar-harness-core/SKILL.md +4 -3
- package/harness-skills/mstar-iteration/SKILL.md +16 -10
- package/harness-skills/mstar-iteration/references/command-shared-invariants.md +62 -0
- package/harness-skills/mstar-iteration/references/phase-2-worktree-lease.md +3 -3
- package/harness-skills/mstar-iteration/references/phase-4-5-pr-delivery.md +1 -1
- package/harness-skills/mstar-iteration/references/phase5-helper-discovery.md +1 -1
- package/harness-skills/mstar-phase-gates/SKILL.md +1 -1
- package/harness-skills/mstar-plan-artifacts/references/knowledge-and-designs.md +2 -2
- package/harness-skills/mstar-plan-artifacts/references/status-and-residuals.md +1 -1
- package/harness-skills/mstar-plan-conventions/SKILL.md +1 -1
- package/harness-skills/mstar-roles/references/_shared/leaf-executor-core.md +11 -1
- package/harness-skills/mstar-roles/references/architect.md +4 -7
- package/harness-skills/mstar-roles/references/code-reviewer.md +1 -4
- package/harness-skills/mstar-roles/references/frontend-dev.md +2 -4
- package/harness-skills/mstar-roles/references/fullstack-dev-shared.md +2 -4
- package/harness-skills/mstar-roles/references/ops-engineer.md +2 -6
- package/harness-skills/mstar-roles/references/product-manager.md +4 -7
- package/harness-skills/mstar-roles/references/prompt-engineer.md +2 -5
- package/harness-skills/mstar-roles/references/qa-engineer.md +2 -6
- package/harness-skills/mstar-roles/references/qc-specialist-shared.md +1 -4
- package/harness-skills/mstar-roles/references/writing-specialist.md +2 -5
- package/harness-skills/mstar-strategy/SKILL.md +5 -35
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,20 @@ The monorepo root [CHANGELOG.md](../../CHANGELOG.md) summarizes cross-surface re
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [3.1.3] - 2026-08-23
|
|
10
|
+
|
|
11
|
+
### Harness
|
|
12
|
+
|
|
13
|
+
- **`mstar audit promote` (CLI)**: selected audit plans can now enter the v2 workflow lifecycle as `type: plan` — `promoteAuditPlans` writes the workflow snapshot (`{HARNESS_DIR}/workflows/<id>/snapshot.json`, one Todo `PlanRow` per selected plan, `id` + `title` + `file`) **before** registering the workflow in root `status.json`, so the snapshot-before-register invariant holds. `--plans` accepts the README Plan column id, stem, or basename; `--workflow` defaults to the `audit-<date>` dir basename; `--harness` defaults to the resolved `{HARNESS_DIR}`. Promote stays an explicit post-selection action — the audit itself never registers (advisory contract preserved).
|
|
14
|
+
- **Engine**: `promoteAuditPlans` exported from `@mstar-harness/engine` (titles come from the audit README index, falling back to `readPlanFileSummary`).
|
|
15
|
+
- **Harness skills**: `mstar-audit` Handoff step 1 now names `mstar audit promote <audit-dir> --plans <ids>` as the first-class v2 path (manual `mstar-plan-artifacts` wording kept as fallback).
|
|
16
|
+
- **Thinned the iteration slash commands** (`/iteration-start`, `/iteration-drive`, `/iteration-loop`) into wrappers: shared PM invariants, session todos, the continuous-execution STOP list, and the assignment-preflight bash (warn-only + `enforcement: hard` fail-fast) now live once in `skills/mstar-iteration/references/command-shared-invariants.md`, and `mstar-iteration` points at it. Frontmatter (`name` / `description` / `agent` / `input`) and each command's unique bits (start grill-me, drive helper discovery, loop do-not-load-grill-me) are unchanged.
|
|
17
|
+
- **Trigger-strong `mstar-iteration` description**: the skill now loads for "start an iteration" / "drive the iteration" / "run an autonomous loop" even when no `/iteration-*` slash command fires; phase labels no longer treat command names as phase names.
|
|
18
|
+
|
|
19
|
+
- Version alignment with harness **3.1.3** (no OpenCode package API change).
|
|
20
|
+
|
|
21
|
+
See root [CHANGELOG.md](../../CHANGELOG.md) **3.1.3**.
|
|
22
|
+
|
|
9
23
|
## [3.1.2] - 2026-08-21
|
|
10
24
|
|
|
11
25
|
### Harness
|
package/dist/mstar.js
CHANGED
|
@@ -63,11 +63,11 @@ __export(exports_engine, {
|
|
|
63
63
|
releaseLease: () => releaseLease,
|
|
64
64
|
registerWorkflow: () => registerWorkflow,
|
|
65
65
|
referenceExists: () => referenceExists,
|
|
66
|
-
redactSecrets: () => redactSecrets,
|
|
67
66
|
readProgressLedger: () => readProgressLedger,
|
|
68
67
|
readJson: () => readJson,
|
|
69
68
|
readHarnessVersion: () => readHarnessVersion,
|
|
70
69
|
pushCadenceProbe: () => pushCadenceProbe,
|
|
70
|
+
promoteAuditPlans: () => promoteAuditPlans,
|
|
71
71
|
planQualityBar: () => planQualityBar,
|
|
72
72
|
planExecutionLeaseLocations: () => planExecutionLeaseLocations,
|
|
73
73
|
parseMstarc: () => parseMstarc,
|
|
@@ -185,11 +185,11 @@ import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync a
|
|
|
185
185
|
import { join as join9 } from "node:path";
|
|
186
186
|
import { copyFileSync, mkdirSync as mkdirSync6, readFileSync as readFileSync8, readdirSync as readdirSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
187
187
|
import { dirname as dirname7, isAbsolute as isAbsolute6, join as join10, relative as relative3, resolve as resolve8, sep as sep2 } from "node:path";
|
|
188
|
-
import { mkdirSync as mkdirSync7, readdirSync as readdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "node:fs";
|
|
189
|
-
import { join as join11, resolve as resolve9 } from "node:path";
|
|
190
|
-
import { existsSync as
|
|
191
|
-
import { basename as
|
|
192
|
-
import { existsSync as
|
|
188
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync7, readdirSync as readdirSync7, readFileSync as readFileSync9, rmdirSync as rmdirSync2, rmSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
189
|
+
import { basename as basename4, join as join11, resolve as resolve9, sep as sep3 } from "node:path";
|
|
190
|
+
import { existsSync as existsSync8, readdirSync as readdirSync8, readFileSync as readFileSync10 } from "node:fs";
|
|
191
|
+
import { basename as basename5, isAbsolute as isAbsolute7, join as join12, relative as relative4, resolve as resolve10, sep as sep4 } from "node:path";
|
|
192
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
193
193
|
import { join as join13 } from "node:path";
|
|
194
194
|
function applyEnforcement(gate, opts) {
|
|
195
195
|
return { ...gate, hardBlocked: opts.hard && gate.violations.length > 0 };
|
|
@@ -1434,40 +1434,41 @@ function validateStatusV2(docOrPath, opts = {}) {
|
|
|
1434
1434
|
violations.push(violation4("medium", "status.workflow.mismatched-type", `workflows[] entry ${JSON.stringify(label)} type ${JSON.stringify(entry.type)} does not match its snapshot type ${JSON.stringify(snapshot.type)} — the root entry mirrors the snapshot; align them`));
|
|
1435
1435
|
}
|
|
1436
1436
|
if (typeof entry.started_at === "string" && typeof snapshot.started_at === "string" && entry.started_at !== snapshot.started_at) {
|
|
1437
|
-
violations.push(violation4("medium", "status.workflow.mismatched-started-at", `workflows[] entry ${JSON.stringify(label)} started_at ${JSON.stringify(entry.started_at)} does not match its snapshot started_at ${JSON.stringify(snapshot.started_at)} — the root entry mirrors the snapshot
|
|
1437
|
+
violations.push(violation4("medium", "status.workflow.mismatched-started-at", `workflows[] entry ${JSON.stringify(label)} started_at ${JSON.stringify(entry.started_at)} does not match its snapshot started_at ${JSON.stringify(snapshot.started_at)} — workflow ${JSON.stringify(label)} collided with another writer (e.g. a concurrent/re-run \`audit promote\` with the same workflow id rewrote the snapshot); the root entry mirrors the snapshot — align them or remove the colliding workflow`));
|
|
1438
1438
|
}
|
|
1439
1439
|
}
|
|
1440
1440
|
}
|
|
1441
1441
|
return { ok: violations.length === 0, violations };
|
|
1442
1442
|
}
|
|
1443
|
+
function registerWorkflowEntryLocked(statusPath, entry) {
|
|
1444
|
+
const harnessDir = dirname5(statusPath);
|
|
1445
|
+
const current = readJson(statusPath);
|
|
1446
|
+
const fresh = Object.keys(current).length === 0;
|
|
1447
|
+
const doc = fresh ? { version: 2, updated_at: todayString(), workflows: [] } : current;
|
|
1448
|
+
if (!fresh && !Array.isArray(doc.workflows)) {
|
|
1449
|
+
throw new Error("refusing to modify status.json: workflows must be an array — a v1 root must be migrated first (run `mstar migrate`)");
|
|
1450
|
+
}
|
|
1451
|
+
const existing = doc.workflows.findIndex((wf) => wf.id === entry.id);
|
|
1452
|
+
if (existing >= 0) {
|
|
1453
|
+
doc.workflows[existing] = entry;
|
|
1454
|
+
} else {
|
|
1455
|
+
doc.workflows.push(entry);
|
|
1456
|
+
}
|
|
1457
|
+
doc.updated_at = todayString();
|
|
1458
|
+
const gate = validateStatusV2(doc, { harnessDir });
|
|
1459
|
+
if (!gate.ok) {
|
|
1460
|
+
throw new Error(`refusing to write invalid status.json: ${gate.violations.map((v) => v.message).join("; ")}`);
|
|
1461
|
+
}
|
|
1462
|
+
writeJson(statusPath, doc);
|
|
1463
|
+
return doc;
|
|
1464
|
+
}
|
|
1443
1465
|
async function registerWorkflow(root, entry) {
|
|
1444
1466
|
const entryGate = validateWorkflowEntry(entry);
|
|
1445
1467
|
if (!entryGate.ok) {
|
|
1446
1468
|
throw new Error(`refusing to register invalid workflow entry: ${entryGate.violations.map((v) => v.message).join("; ")}`);
|
|
1447
1469
|
}
|
|
1448
1470
|
const statusPath = resolve5(root);
|
|
1449
|
-
|
|
1450
|
-
return withStatusWriteLock(statusPath, () => {
|
|
1451
|
-
const current = readJson(statusPath);
|
|
1452
|
-
const fresh = Object.keys(current).length === 0;
|
|
1453
|
-
const doc = fresh ? { version: 2, updated_at: todayString(), workflows: [] } : current;
|
|
1454
|
-
if (!fresh && !Array.isArray(doc.workflows)) {
|
|
1455
|
-
throw new Error("refusing to modify status.json: workflows must be an array — a v1 root must be migrated first (run `mstar migrate`)");
|
|
1456
|
-
}
|
|
1457
|
-
const existing = doc.workflows.findIndex((wf) => wf.id === entry.id);
|
|
1458
|
-
if (existing >= 0) {
|
|
1459
|
-
doc.workflows[existing] = entry;
|
|
1460
|
-
} else {
|
|
1461
|
-
doc.workflows.push(entry);
|
|
1462
|
-
}
|
|
1463
|
-
doc.updated_at = todayString();
|
|
1464
|
-
const gate = validateStatusV2(doc, { harnessDir });
|
|
1465
|
-
if (!gate.ok) {
|
|
1466
|
-
throw new Error(`refusing to write invalid status.json: ${gate.violations.map((v) => v.message).join("; ")}`);
|
|
1467
|
-
}
|
|
1468
|
-
writeJson(statusPath, doc);
|
|
1469
|
-
return doc;
|
|
1470
|
-
});
|
|
1471
|
+
return withStatusWriteLock(statusPath, () => registerWorkflowEntryLocked(statusPath, entry));
|
|
1471
1472
|
}
|
|
1472
1473
|
async function unregisterWorkflow(root, id) {
|
|
1473
1474
|
if (typeof id !== "string" || id.trim() === "") {
|
|
@@ -3012,9 +3013,11 @@ async function applyMigratePlan(plan) {
|
|
|
3012
3013
|
if (!gate2.ok) {
|
|
3013
3014
|
throw new Error(`refusing to apply migration: invalid project register: ${gate2.violations.map((v) => v.message).join("; ")}`);
|
|
3014
3015
|
}
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3016
|
+
if (Object.keys(plan.register.data.entries ?? {}).length > 0) {
|
|
3017
|
+
const filePath = projectTargetOf(plan.register.file);
|
|
3018
|
+
mkdirSync6(dirname7(filePath), { recursive: true });
|
|
3019
|
+
writeJson(filePath, plan.register.data);
|
|
3020
|
+
}
|
|
3018
3021
|
}
|
|
3019
3022
|
if (plan.roadmap !== null) {
|
|
3020
3023
|
const filePath = projectTargetOf(plan.roadmap.file);
|
|
@@ -3471,60 +3474,6 @@ function validateAuditStatusBlocks(planText) {
|
|
|
3471
3474
|
});
|
|
3472
3475
|
return { ok: violations.length === 0, violations };
|
|
3473
3476
|
}
|
|
3474
|
-
function buildLineStarts(text) {
|
|
3475
|
-
const starts = [0];
|
|
3476
|
-
for (let i = 0;i < text.length; i++) {
|
|
3477
|
-
if (text[i] === `
|
|
3478
|
-
`)
|
|
3479
|
-
starts.push(i + 1);
|
|
3480
|
-
}
|
|
3481
|
-
return starts;
|
|
3482
|
-
}
|
|
3483
|
-
function lineAt(starts, index) {
|
|
3484
|
-
let lo = 0;
|
|
3485
|
-
let hi = starts.length - 1;
|
|
3486
|
-
while (lo < hi) {
|
|
3487
|
-
const mid = lo + hi + 1 >> 1;
|
|
3488
|
-
if (starts[mid] <= index)
|
|
3489
|
-
lo = mid;
|
|
3490
|
-
else
|
|
3491
|
-
hi = mid - 1;
|
|
3492
|
-
}
|
|
3493
|
-
return lo + 1;
|
|
3494
|
-
}
|
|
3495
|
-
function redactSecrets(text, filePath) {
|
|
3496
|
-
const starts = buildLineStarts(text);
|
|
3497
|
-
const marker = (type, index) => `[REDACTED ${type}@${lineAt(starts, index)}${filePath === undefined ? "" : ` in ${filePath}`}]`;
|
|
3498
|
-
const replacements = [];
|
|
3499
|
-
const findings = [];
|
|
3500
|
-
for (const pattern of WHOLE_MATCH_PATTERNS) {
|
|
3501
|
-
for (const match of text.matchAll(pattern.re)) {
|
|
3502
|
-
if (match.index === undefined)
|
|
3503
|
-
continue;
|
|
3504
|
-
replacements.push({ index: match.index, length: match[0].length, text: marker(pattern.type, match.index) });
|
|
3505
|
-
findings.push({ line: lineAt(starts, match.index), type: pattern.type });
|
|
3506
|
-
}
|
|
3507
|
-
}
|
|
3508
|
-
for (const pattern of VALUE_PATTERNS) {
|
|
3509
|
-
for (const match of text.matchAll(pattern.re)) {
|
|
3510
|
-
if (match.index === undefined)
|
|
3511
|
-
continue;
|
|
3512
|
-
const type = pattern.typeOf(match[2]);
|
|
3513
|
-
const replacement = `${match[1]}${match[2]}${match[3]}${match[4]}${marker(type, match.index)}`;
|
|
3514
|
-
replacements.push({ index: match.index, length: match[0].length, text: replacement });
|
|
3515
|
-
findings.push({ line: lineAt(starts, match.index), type });
|
|
3516
|
-
}
|
|
3517
|
-
}
|
|
3518
|
-
replacements.sort((a, b) => b.index - a.index);
|
|
3519
|
-
let out = text;
|
|
3520
|
-
for (const r of replacements)
|
|
3521
|
-
out = out.slice(0, r.index) + r.text + out.slice(r.index + r.length);
|
|
3522
|
-
const deduped = new Map;
|
|
3523
|
-
for (const f of findings)
|
|
3524
|
-
deduped.set(`${f.line}:${f.type}`, f);
|
|
3525
|
-
const sorted = [...deduped.values()].sort((a, b) => a.line - b.line || a.type.localeCompare(b.type));
|
|
3526
|
-
return { text: out, findings: sorted };
|
|
3527
|
-
}
|
|
3528
3477
|
function slugify(title) {
|
|
3529
3478
|
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3530
3479
|
}
|
|
@@ -3659,6 +3608,137 @@ function scaffoldAuditPlan(outDir, findings, options = {}) {
|
|
|
3659
3608
|
}));
|
|
3660
3609
|
return { outDir: resolve9(outDir), date, files: written, nextNumber: next };
|
|
3661
3610
|
}
|
|
3611
|
+
async function promoteAuditPlans(outDir, selected, options) {
|
|
3612
|
+
if (selected.length === 0) {
|
|
3613
|
+
throw new Error("promoteAuditPlans: at least one plan id must be selected (--plans 001,002,…)");
|
|
3614
|
+
}
|
|
3615
|
+
if (typeof options.harnessDir !== "string" || options.harnessDir.trim() === "") {
|
|
3616
|
+
throw new Error("promoteAuditPlans: options.harnessDir is required (must contain status.json + workflows/)");
|
|
3617
|
+
}
|
|
3618
|
+
const workflowId = options.workflowId ?? basename4(resolve9(outDir));
|
|
3619
|
+
assertSafePathComponent(workflowId, "workflow id");
|
|
3620
|
+
const harnessDir = resolve9(options.harnessDir);
|
|
3621
|
+
const statusPath = join11(harnessDir, "status.json");
|
|
3622
|
+
const workflowDir = join11(harnessDir, "workflows", workflowId);
|
|
3623
|
+
const snapshotPath = join11(workflowDir, WORKFLOW_SNAPSHOT_FILE);
|
|
3624
|
+
const planFiles = resolveSelectedPlanFiles(outDir, selected);
|
|
3625
|
+
const indexRows = readExecutionOrderIndex(outDir);
|
|
3626
|
+
const plans = planFiles.map((planFile) => {
|
|
3627
|
+
const stem = planFile.replace(/\.md$/, "");
|
|
3628
|
+
const num = stem.slice(0, 3);
|
|
3629
|
+
const indexRow = indexRows.get(num);
|
|
3630
|
+
const title = indexRow?.title ?? readPlanFileSummary(join11(outDir, planFile)).title;
|
|
3631
|
+
return {
|
|
3632
|
+
id: stem,
|
|
3633
|
+
title,
|
|
3634
|
+
file: planFileRel(outDir, planFile),
|
|
3635
|
+
status: "Todo"
|
|
3636
|
+
};
|
|
3637
|
+
});
|
|
3638
|
+
const now = new Date;
|
|
3639
|
+
const snapshot = {
|
|
3640
|
+
schema_version: 1,
|
|
3641
|
+
id: workflowId,
|
|
3642
|
+
type: "plan",
|
|
3643
|
+
status: "running",
|
|
3644
|
+
started_at: now.toISOString(),
|
|
3645
|
+
updated_at: now.toISOString().slice(0, 10),
|
|
3646
|
+
plans
|
|
3647
|
+
};
|
|
3648
|
+
const entry = {
|
|
3649
|
+
id: workflowId,
|
|
3650
|
+
type: "plan",
|
|
3651
|
+
started_at: snapshot.started_at,
|
|
3652
|
+
dir: `workflows/${workflowId}`
|
|
3653
|
+
};
|
|
3654
|
+
const entryGate = validateWorkflowEntry(entry);
|
|
3655
|
+
if (!entryGate.ok) {
|
|
3656
|
+
throw new Error(`refusing to register invalid workflow entry: ${entryGate.violations.map((v) => v.message).join("; ")}`);
|
|
3657
|
+
}
|
|
3658
|
+
await withStatusWriteLock(statusPath, () => {
|
|
3659
|
+
if (existsSync7(snapshotPath)) {
|
|
3660
|
+
throw new Error(`refusing to promote audit plans: workflow ${JSON.stringify(workflowId)} already exists ` + `(snapshot at ${snapshotPath}) — re-promote would drop its registered plan rows; ` + `remove that workflow before promoting again`);
|
|
3661
|
+
}
|
|
3662
|
+
mkdirSync7(workflowDir, { recursive: true });
|
|
3663
|
+
try {
|
|
3664
|
+
writeJson(snapshotPath, snapshot);
|
|
3665
|
+
registerWorkflowEntryLocked(statusPath, entry);
|
|
3666
|
+
} catch (error) {
|
|
3667
|
+
rmSync(snapshotPath, { force: true });
|
|
3668
|
+
try {
|
|
3669
|
+
if (readdirSync7(workflowDir).length === 0) {
|
|
3670
|
+
rmdirSync2(workflowDir);
|
|
3671
|
+
}
|
|
3672
|
+
} catch {}
|
|
3673
|
+
throw error;
|
|
3674
|
+
}
|
|
3675
|
+
return { workflowId, snapshotPath };
|
|
3676
|
+
});
|
|
3677
|
+
return { workflowId, snapshotPath };
|
|
3678
|
+
}
|
|
3679
|
+
function resolveSelectedPlanFiles(outDir, selected) {
|
|
3680
|
+
const files = readdirSync7(outDir).filter((f) => /^\d{3}-.*\.md$/.test(f)).sort();
|
|
3681
|
+
const byNum = new Map;
|
|
3682
|
+
const byStem = new Map;
|
|
3683
|
+
for (const file of files) {
|
|
3684
|
+
const stem = file.replace(/\.md$/, "");
|
|
3685
|
+
if (!byNum.has(stem.slice(0, 3))) {
|
|
3686
|
+
byNum.set(stem.slice(0, 3), file);
|
|
3687
|
+
}
|
|
3688
|
+
byStem.set(stem, file);
|
|
3689
|
+
}
|
|
3690
|
+
const resolved = [];
|
|
3691
|
+
const seen = new Set;
|
|
3692
|
+
for (const id of selected) {
|
|
3693
|
+
const file = byNum.get(id) ?? byStem.get(id) ?? byStem.get(id.replace(/\.md$/, ""));
|
|
3694
|
+
if (file === undefined) {
|
|
3695
|
+
throw new Error(`promoteAuditPlans: selected plan ${JSON.stringify(id)} does not match any NNN-*.md file in ${resolve9(outDir)}`);
|
|
3696
|
+
}
|
|
3697
|
+
if (!seen.has(file)) {
|
|
3698
|
+
seen.add(file);
|
|
3699
|
+
resolved.push(file);
|
|
3700
|
+
}
|
|
3701
|
+
}
|
|
3702
|
+
return resolved;
|
|
3703
|
+
}
|
|
3704
|
+
function readExecutionOrderIndex(outDir) {
|
|
3705
|
+
const readmePath = join11(outDir, "README.md");
|
|
3706
|
+
let text;
|
|
3707
|
+
try {
|
|
3708
|
+
text = readFileSync9(readmePath, "utf8");
|
|
3709
|
+
} catch {
|
|
3710
|
+
return new Map;
|
|
3711
|
+
}
|
|
3712
|
+
const rows = new Map;
|
|
3713
|
+
const lines = text.split(`
|
|
3714
|
+
`);
|
|
3715
|
+
let inSection = false;
|
|
3716
|
+
for (const line of lines) {
|
|
3717
|
+
if (/^##\s+Execution order & status/.test(line)) {
|
|
3718
|
+
inSection = true;
|
|
3719
|
+
continue;
|
|
3720
|
+
}
|
|
3721
|
+
if (inSection && /^#/.test(line)) {
|
|
3722
|
+
break;
|
|
3723
|
+
}
|
|
3724
|
+
if (!inSection)
|
|
3725
|
+
continue;
|
|
3726
|
+
const cells = line.split(/(?<!\\)\|/).map((c) => c.trim());
|
|
3727
|
+
if (cells.length >= 3 && /^\d{3}$/.test(cells[1])) {
|
|
3728
|
+
rows.set(cells[1], { title: cells[2].replace(/\\\|/g, "|") });
|
|
3729
|
+
}
|
|
3730
|
+
}
|
|
3731
|
+
return rows;
|
|
3732
|
+
}
|
|
3733
|
+
function planFileRel(outDir, planFile) {
|
|
3734
|
+
const resolved = resolve9(outDir);
|
|
3735
|
+
const parts = resolved.split(sep3);
|
|
3736
|
+
const plansIdx = parts.lastIndexOf("plans");
|
|
3737
|
+
if (plansIdx >= 0) {
|
|
3738
|
+
return `${parts.slice(plansIdx + 1).join(sep3)}${sep3}${planFile}`;
|
|
3739
|
+
}
|
|
3740
|
+
return planFile;
|
|
3741
|
+
}
|
|
3662
3742
|
function violation10(severity, code, message, fix) {
|
|
3663
3743
|
return { ok: false, severity, code, message, fix };
|
|
3664
3744
|
}
|
|
@@ -3875,7 +3955,7 @@ function referenceExists(repoRoot, docText) {
|
|
|
3875
3955
|
for (const { ref, isSymbol, module } of refs) {
|
|
3876
3956
|
if (!isSymbol || module === undefined) {
|
|
3877
3957
|
const candidate = ref.replace(LINE_SUFFIX_RE, "").replace(ANCHOR_RE, "");
|
|
3878
|
-
if (
|
|
3958
|
+
if (existsSync8(resolve10(repoRoot, candidate))) {
|
|
3879
3959
|
checked++;
|
|
3880
3960
|
} else {
|
|
3881
3961
|
violations.push(violation10("medium", "compound.reference.missing-file", `referenced path \`${ref}\` does not exist under ${repoRoot} (compound-refresh Phase 2: referenced code still exists?)`, "update the doc to reference an existing path, or delete the stale reference"));
|
|
@@ -3906,7 +3986,7 @@ function collectKnowledgeDocs(dir) {
|
|
|
3906
3986
|
if (entry.isDirectory()) {
|
|
3907
3987
|
stack.push(full);
|
|
3908
3988
|
} else if (entry.name.endsWith(".md") && entry.name !== "README.md" && entry.name !== "index.md") {
|
|
3909
|
-
docs.push(relative4(dir, full).split(
|
|
3989
|
+
docs.push(relative4(dir, full).split(sep4).join("/"));
|
|
3910
3990
|
}
|
|
3911
3991
|
}
|
|
3912
3992
|
}
|
|
@@ -3923,7 +4003,7 @@ function normalizeIndexRef(cell) {
|
|
|
3923
4003
|
function assertIndexRows(knowledgeDir) {
|
|
3924
4004
|
const violations = [];
|
|
3925
4005
|
const readmePath = join12(knowledgeDir, "README.md");
|
|
3926
|
-
if (!
|
|
4006
|
+
if (!existsSync8(readmePath)) {
|
|
3927
4007
|
violations.push(violation10("medium", "compound.index.missing-readme", `missing ${readmePath} — the knowledge index is required (mstar-compound Phase 6: every doc gets a README.md row)`, "create knowledge/README.md with a Document / Source Plan / Description / Status table"));
|
|
3928
4008
|
return { ok: false, violations };
|
|
3929
4009
|
}
|
|
@@ -3955,7 +4035,7 @@ function compoundRefreshScope(harnessDir, projectRoot) {
|
|
|
3955
4035
|
];
|
|
3956
4036
|
}
|
|
3957
4037
|
function isFileLikeRoot(root) {
|
|
3958
|
-
return /^[^.]*\.[A-Za-z0-9]{1,10}$/.test(
|
|
4038
|
+
return /^[^.]*\.[A-Za-z0-9]{1,10}$/.test(basename5(root));
|
|
3959
4039
|
}
|
|
3960
4040
|
function scopeGuard(path, allowedRoots) {
|
|
3961
4041
|
const resolved = resolve10(path);
|
|
@@ -3964,7 +4044,7 @@ function scopeGuard(path, allowedRoots) {
|
|
|
3964
4044
|
if (isFileLikeRoot(r)) {
|
|
3965
4045
|
if (resolved === r)
|
|
3966
4046
|
return { ok: true, violations: [] };
|
|
3967
|
-
} else if (resolved === r || resolved.startsWith(r +
|
|
4047
|
+
} else if (resolved === r || resolved.startsWith(r + sep4)) {
|
|
3968
4048
|
return { ok: true, violations: [] };
|
|
3969
4049
|
}
|
|
3970
4050
|
}
|
|
@@ -4186,7 +4266,7 @@ function validateRoleMapping(rolesDir, options = {}) {
|
|
|
4186
4266
|
const violations = [];
|
|
4187
4267
|
const referenceById = new Map(mapping.map((m) => [m.agentId, m.reference]));
|
|
4188
4268
|
for (const { agentId, reference } of mapping) {
|
|
4189
|
-
if (!
|
|
4269
|
+
if (!existsSync9(join13(rolesDir, reference))) {
|
|
4190
4270
|
violations.push(violation12("medium", "roles.mapping.reference.missing", `role "${agentId}" maps to ${reference} which does not exist under ${rolesDir} (mstar-roles § Role Reference Mapping)`, `create ${join13(rolesDir, reference)} or fix the mapping row`));
|
|
4191
4271
|
}
|
|
4192
4272
|
}
|
|
@@ -4382,7 +4462,7 @@ var SEVERITY_ORDER, MSTARC_FILE = ".mstarc", MSTARC_SECTION = "config", MSTARC_H
|
|
|
4382
4462
|
!.agents/knowledge/**
|
|
4383
4463
|
!.agents/specs/
|
|
4384
4464
|
!.agents/specs/**
|
|
4385
|
-
`, GITIGNORE_PROCESS_ENTRIES, GITIGNORE_PROCESS_ENTRIES_AGENTS, DATE_PART, RFC3339_Z_RE, DATE_ONLY_RE, STATUS_WRITE_LOCKDIR = ".status-write.lockdir", LOCKDIR_HOLDER_PID = "holder.pid", heldLockDirs, BRANCH_FORMS_HINT = '"Working branch: <existing>" | "Working branch: create <new> from <base>" | "Branch policy: direct on <branch> — <reason>"', REQUIRED_FIELDS, ASSIGNMENT_ENFORCEMENT_BOLD_RE, ASSIGNMENT_ENFORCEMENT_PLAIN_RE, COMPASS_ENFORCEMENT_RE, ASSIGNMENT_BODY_START_RE, ASSIGNMENT_HEADING_RE, ASSIGNMENT_FIELD_RE, WORKFLOW_SNAPSHOT_FILE = "snapshot.json", WORKFLOW_LIFECYCLE_STATUSES, WORKFLOW_TERMINAL_STATUSES, WORKFLOW_LIFECYCLE_TYPES, DATE_RE, PLAN_STATUSES, RESIDUAL_DECISIONS, RESIDUAL_LIFECYCLES, validateStatus, DEFAULT_PROBE_TIMEOUT_MS = 1e4, QC_ALIGNMENT_FIELDS, SddScriptError, GIT_CAPTURE_MAX_BYTES, COMPASS_STATUSES, DATE_RE2, PLAN_STATUS_DONE = "Done", COMPASS_FILE = "delivery-compass.md", INDEX_README = "README.md", INDEX_HEADER = "| Iteration | Path | Description | Status |", PROJECT_ROADMAP_FILE = "roadmap.md", PROJECT_REFERENCES_DIR = "references", PROJECT_REGISTER_FILE = "residuals.json", _DEFAULT_PROJECT = "_default", ROADMAP_STATUSES, DATE_RE3, ROLLUP_FIELDS, MIGRATE_STATUS_FILE = "status.json", ARCHIVED_STATUS_V1_FILE = "archived/status.v1.json", NOTES_LEDGER_FILE = "notes.jsonl", DATE_RE4, ROOT_METADATA_LIFT_KEYS, RAW_GROUP = "__raw", isMap = (v) => typeof v === "object" && v !== null && !Array.isArray(v), HEX_RE, OKLCH_RE, PX_RE, PLACEHOLDER_RE, TYPOGRAPHY_PROPS, REF_RE, REF_GROUPS, isPlaceholder = (value) => PLACEHOLDER_RE.test(value), PARITY_GROUPS, GRAY_STEPS, ALPHA_STEPS, ACCENT_SCALES, L3_BODY_ITEM_IDS, LEVEL_RANK, AUDIT_PRIORITIES, AUDIT_EFFORTS, AUDIT_RISKS, AUDIT_CATEGORIES, AUDIT_STATUS_FIELDS,
|
|
4465
|
+
`, GITIGNORE_PROCESS_ENTRIES, GITIGNORE_PROCESS_ENTRIES_AGENTS, DATE_PART, RFC3339_Z_RE, DATE_ONLY_RE, STATUS_WRITE_LOCKDIR = ".status-write.lockdir", LOCKDIR_HOLDER_PID = "holder.pid", heldLockDirs, BRANCH_FORMS_HINT = '"Working branch: <existing>" | "Working branch: create <new> from <base>" | "Branch policy: direct on <branch> — <reason>"', REQUIRED_FIELDS, ASSIGNMENT_ENFORCEMENT_BOLD_RE, ASSIGNMENT_ENFORCEMENT_PLAIN_RE, COMPASS_ENFORCEMENT_RE, ASSIGNMENT_BODY_START_RE, ASSIGNMENT_HEADING_RE, ASSIGNMENT_FIELD_RE, WORKFLOW_SNAPSHOT_FILE = "snapshot.json", WORKFLOW_LIFECYCLE_STATUSES, WORKFLOW_TERMINAL_STATUSES, WORKFLOW_LIFECYCLE_TYPES, DATE_RE, PLAN_STATUSES, RESIDUAL_DECISIONS, RESIDUAL_LIFECYCLES, validateStatus, DEFAULT_PROBE_TIMEOUT_MS = 1e4, QC_ALIGNMENT_FIELDS, SddScriptError, GIT_CAPTURE_MAX_BYTES, COMPASS_STATUSES, DATE_RE2, PLAN_STATUS_DONE = "Done", COMPASS_FILE = "delivery-compass.md", INDEX_README = "README.md", INDEX_HEADER = "| Iteration | Path | Description | Status |", PROJECT_ROADMAP_FILE = "roadmap.md", PROJECT_REFERENCES_DIR = "references", PROJECT_REGISTER_FILE = "residuals.json", _DEFAULT_PROJECT = "_default", ROADMAP_STATUSES, DATE_RE3, ROLLUP_FIELDS, MIGRATE_STATUS_FILE = "status.json", ARCHIVED_STATUS_V1_FILE = "archived/status.v1.json", NOTES_LEDGER_FILE = "notes.jsonl", DATE_RE4, ROOT_METADATA_LIFT_KEYS, RAW_GROUP = "__raw", isMap = (v) => typeof v === "object" && v !== null && !Array.isArray(v), HEX_RE, OKLCH_RE, PX_RE, PLACEHOLDER_RE, TYPOGRAPHY_PROPS, REF_RE, REF_GROUPS, isPlaceholder = (value) => PLACEHOLDER_RE.test(value), PARITY_GROUPS, GRAY_STEPS, ALPHA_STEPS, ACCENT_SCALES, L3_BODY_ITEM_IDS, LEVEL_RANK, AUDIT_PRIORITIES, AUDIT_EFFORTS, AUDIT_RISKS, AUDIT_CATEGORIES, AUDIT_STATUS_FIELDS, escapeCell = (value) => value.replace(/\|/g, "\\|"), truncate = (value, max) => value.length > max ? `${value.slice(0, max)}…` : value, KNOWLEDGE_REQUIRED_FIELDS, KNOWLEDGE_PROBLEM_TYPES, KNOWLEDGE_BUG_PROBLEM_TYPES, KNOWLEDGE_KNOWLEDGE_PROBLEM_TYPES, KNOWLEDGE_SEVERITIES, KNOWLEDGE_RESOLUTION_TYPES, KNOWLEDGE_CATEGORY_MAP, DATE_RE5, isMap2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v), REF_EXT_RE, SYMBOL_REF_RE, SCHEME_RE, LINE_SUFFIX_RE, ANCHOR_RE, WALK_SKIP_DIRS, MAX_WALK_FILES = 5000, COMMENT_INTRODUCER = "(?:\\/\\/|\\/\\*|#|;|--|\\s\\*)", REMOVAL_PATH_PATTERNS, TASK_ARTIFACT_RE, SDD_DEEPLINK_RE, TEST_FILE_PATH_RE, TEST_FILE_PHRASE_RE, COMMAND_PROMPT_RE, RUNNER_RE, OUTPUT_TOKEN_RE, PLACEHOLDER_TOKEN_RE, ELLIPSIS_RE, NEGATION_RE, WORKFLOW_VERB_START_RE, PRONOUN_RE, DESCRIPTION_MAX_WORDS = 120, REQUIRED_STRATEGY_SECTIONS, ROLE_MAPPING, SHARED_FAMILIES, DEV_TRACK_PARAMS, QC_REVIEWER_PARAMS, LOAD_ORDER_HEADING_RE, FIVE_QUESTION_SECTIONS, HEADING_RE, RUNTIME_HEADING_ALIASES;
|
|
4386
4466
|
var init_engine = __esm(() => {
|
|
4387
4467
|
SEVERITY_ORDER = ["critical", "high", "medium", "low", "nit"];
|
|
4388
4468
|
CONFIG_KEYS = {
|
|
@@ -4497,20 +4577,6 @@ var init_engine = __esm(() => {
|
|
|
4497
4577
|
"direction"
|
|
4498
4578
|
];
|
|
4499
4579
|
AUDIT_STATUS_FIELDS = ["Priority", "Effort", "Risk", "Depends on", "Category", "Planned at"];
|
|
4500
|
-
WHOLE_MATCH_PATTERNS = [
|
|
4501
|
-
{ type: "private-key", re: /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/g },
|
|
4502
|
-
{ type: "aws-access-key", re: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g },
|
|
4503
|
-
{ type: "github-token", re: /\bgh[pousr]_[A-Za-z0-9]{36,}\b/g },
|
|
4504
|
-
{ type: "slack-token", re: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g },
|
|
4505
|
-
{ type: "jwt", re: /\beyJ[A-Za-z0-9_-]{10,1024}\.[A-Za-z0-9_-]{10,1024}\.[A-Za-z0-9_-]{10,1024}\b/g },
|
|
4506
|
-
{ type: "api-secret-key", re: /\bsk-[A-Za-z0-9-]{20,}\b/g }
|
|
4507
|
-
];
|
|
4508
|
-
VALUE_PATTERNS = [
|
|
4509
|
-
{
|
|
4510
|
-
typeOf: (key) => key.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().replace(/[_-]+/g, "-"),
|
|
4511
|
-
re: /(["']?)\b(password|passwd|api[_-]?key|access[_-]?token|auth[_-]?token|secret|token)\b(["']?)(\s*[:=]\s*)("[^"\n]{8,}"|'[^'\n]{8,}'|[A-Za-z0-9_./+\-=]{16,})/gi
|
|
4512
|
-
}
|
|
4513
|
-
];
|
|
4514
4580
|
KNOWLEDGE_REQUIRED_FIELDS = ["module", "date", "problem_type", "category", "severity"];
|
|
4515
4581
|
KNOWLEDGE_PROBLEM_TYPES = [
|
|
4516
4582
|
"build_error",
|
|
@@ -5143,7 +5209,28 @@ var MorningStarHarnessPlugin = async () => {
|
|
|
5143
5209
|
defaultStatusLogger("error", "hard-gate blocked (hardBlocked=true) — refusal requires a host refusal channel");
|
|
5144
5210
|
}
|
|
5145
5211
|
} else if (input.tool === "edit") {
|
|
5146
|
-
|
|
5212
|
+
classifyDirResolvers = await dirResolversLoader.load();
|
|
5213
|
+
if (harnessDocKindOfTarget(filePath) === null)
|
|
5214
|
+
return;
|
|
5215
|
+
const oldString = args.oldString;
|
|
5216
|
+
const newString = args.newString;
|
|
5217
|
+
const replaceAll = args.replaceAll;
|
|
5218
|
+
let patchedDoc;
|
|
5219
|
+
if (typeof oldString === "string" && oldString.length > 0 && typeof newString === "string" && (replaceAll === undefined || typeof replaceAll === "boolean")) {
|
|
5220
|
+
try {
|
|
5221
|
+
const text = fs.readFileSync(filePath, "utf8");
|
|
5222
|
+
const hits = text.split(oldString).length - 1;
|
|
5223
|
+
if (replaceAll === true || hits === 1) {
|
|
5224
|
+
const patched = text.split(oldString).join(newString);
|
|
5225
|
+
try {
|
|
5226
|
+
patchedDoc = JSON.parse(patched);
|
|
5227
|
+
} catch {
|
|
5228
|
+
patchedDoc = null;
|
|
5229
|
+
}
|
|
5230
|
+
}
|
|
5231
|
+
} catch {}
|
|
5232
|
+
}
|
|
5233
|
+
const gate2 = patchedDoc !== undefined ? await validateStatusWrite(filePath, { doc: patchedDoc }) : await validateStatusWrite(filePath);
|
|
5147
5234
|
if (gate2?.hardBlocked) {
|
|
5148
5235
|
defaultStatusLogger("error", "hard-gate blocked (hardBlocked=true) — refusal requires a host refusal channel");
|
|
5149
5236
|
}
|
|
@@ -15,114 +15,33 @@ Drive the active Morning Star iteration forward. **Boot loads skills; this comma
|
|
|
15
15
|
|
|
16
16
|
**Done 定义**:**仅** Phase 5 §5.5 exit checklist 全 `[x]`。**Phase 3 close ≠ Done;Phase 4 开 PR ≠ Done。**
|
|
17
17
|
|
|
18
|
-
##
|
|
18
|
+
## 共享 invariants / preflight / todos / STOP
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
**合法 STOP(仅此升级用户)**:
|
|
23
|
-
|
|
24
|
-
- §2.0 / Phase 4 branch metadata 缺失(`iteration_base_branch` / `target_branch`)
|
|
25
|
-
- **`Blocked`**:真冲突、secrets、不可逆范围缺口、Phase 5 多轮仍无法 merge-ready
|
|
26
|
-
- 用户本轮显式打断
|
|
27
|
-
|
|
28
|
-
**Turn 收束纪律**:若 host 即将结束本轮,最后一条 assistant 内容必须是 **in-flight 动作**(invoke 已发出、或写明下一 dispatch 的 Assignment 字段),**不得**以对用户的确认问句结尾。
|
|
29
|
-
|
|
30
|
-
## PM invariants(Phase 2–5 全程有效)
|
|
31
|
-
|
|
32
|
-
| 禁止(PM 线程) | 必须 |
|
|
33
|
-
|-----------------|------|
|
|
34
|
-
| Write/Edit/Shell 产品代码、写测试、跑 QC 审查(Phase 2) | 每条 implement/QC/QA Assignment ⇒ **1 次 `Task`** |
|
|
35
|
-
| **多 task plan 用 inline 大包派发**(整份 plan / T1–Tn 贴进一个 dev Assignment) | **SDD**:`mstar-sdd` per-task 循环 — `mstar-harness sdd task-brief` → implementer → `mstar-harness sdd review-package` → task reviewer → `progress.md` |
|
|
36
|
-
| 只写 Assignment 就进入下一 gate | 同轮 dispatch:`Subagent invokes issued: N`(N = Assignment 条数) |
|
|
37
|
-
| 最后一个 plan `Done` 后直接开 PR / 汇报结束 | **Phase 3 → 4 → 5** 顺序执行 |
|
|
38
|
-
| Phase 4 开 PR 后停止 | Phase 5 loop 至 merge-ready;**禁止**未过 §5.5 就结束会话 |
|
|
39
|
-
| Phase 5 自己改产品代码 | 需改产品代码时 **dispatch** `fullstack-dev` / `ops-engineer` |
|
|
40
|
-
|
|
41
|
-
派发细则 → **`mstar-dispatch-gates`** + **`mstar-host`**。Phase 3 细则 → **`mstar-iteration` §3** + **`mstar-compound`**。
|
|
42
|
-
|
|
43
|
-
**Session todos**:
|
|
44
|
-
|
|
45
|
-
| Todo id | 何时追加 | 何时可勾掉 |
|
|
46
|
-
|---------|----------|------------|
|
|
47
|
-
| plan-wave todos | 进入 Phase 2 | 各 plan `Done` |
|
|
48
|
-
| `phase-3-iteration-close` | 仅剩 1 个非 `Done` plan | Phase 3 §3.5 exit 全 `[x]` |
|
|
49
|
-
| `phase-4-create-pr` | Phase 3 完成后 | PR 已创建并记录 URL/number |
|
|
50
|
-
| `phase-5-pr-merge-ready` | Phase 4 完成后 | Phase 5 §5.5 exit 全 `[x]` |
|
|
20
|
+
Phase 2–5 共享内容(PM invariants、assignment preflight、session todos、continuous-execution STOP)→ **`mstar-iteration/references/command-shared-invariants.md`**(SSOT;不在本命令重复)。
|
|
51
21
|
|
|
52
22
|
## Boot
|
|
53
23
|
|
|
54
|
-
|
|
55
|
-
2. `mstar-roles` → `references/project-manager.md`
|
|
56
|
-
3. `mstar-iteration` → **§ Phase 2–5**(§3 close gate;§4 PR delivery;§5 merge-ready loop)
|
|
57
|
-
4. `mstar-compound` — Phase 3 §3.2 前加载(含 Phase 6 索引登记)
|
|
58
|
-
5. `mstar-dispatch-gates` + host reference
|
|
59
|
-
6. **`mstar-sdd`** — **before first implement dispatch** in Phase 2(per-task loop SSOT;多 task plan 默认 `Execution mode: sdd`)
|
|
60
|
-
7. `mstar-review-qc` — before first QC dispatch in Phase 2
|
|
61
|
-
8. `mstar-plan-artifacts`, `mstar-plan-conventions`, `mstar-branch-worktree`
|
|
62
|
-
9. **`mstar-iteration/references/phase-2-worktree-lease.md`** — Phase 2 control worktree、`execution_lease`、serial `integration_merge_lease`(§2.0 #5 未 waive)
|
|
24
|
+
按 **`mstar-iteration`** Load order 加载(`mstar-harness-core` → `mstar-roles` → `references/project-manager.md` → `mstar-iteration` § Phase 2–5 + `command-shared-invariants.md` → `mstar-compound` → `mstar-dispatch-gates` + host reference → **`mstar-sdd`**(first implement dispatch 前)→ `mstar-review-qc`(first QC 前)→ `mstar-plan-artifacts` / `mstar-plan-conventions` / `mstar-branch-worktree` → **`mstar-iteration/references/phase-2-worktree-lease.md`**)。完整 load list → **`mstar-roles`**。
|
|
63
25
|
|
|
64
26
|
## Phase 2: Autonomous Execute
|
|
65
27
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
**Assignment preflight**(`mstar-harness` bin 未安装时静默跳过): 在每次 implement/QC/QA 派发前(**SDD** 下为最新 `{SDD_DIR}/task-N-brief.md` 或临时写盘的 Assignment),若本机装有 CLI,校验最新 Assignment。模式由迭代 compass frontmatter 的 `enforcement` 键决定(Slice 5):
|
|
69
|
-
|
|
70
|
-
- **默认(compass 无 `enforcement: hard`)— 可选 warn-only**:exit 1 仅提示,不阻断派发(Slice 3 行为不变):
|
|
71
|
-
|
|
72
|
-
```bash
|
|
73
|
-
command -v mstar-harness >/dev/null 2>&1 && mstar-harness dispatch validate "<latest-assignment-file>"
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
- **`enforcement: hard`(迭代 compass frontmatter 声明)— fail-fast**:校验失败即 `exit 1` 阻断派发(bin 缺失仍静默跳过):
|
|
28
|
+
Execute **`mstar-iteration` § Phase 2** exactly(§2.0 五道闸 → §2.1 session todos → §2.2 backlog → §2.3 integration branch + control worktree → §2.4 per-plan loop(lease-gated;SDD per-task;QC tri N=3 + QA;serial merge)→ §2.5 dispatch-first → §2.6 push 纪律)。全部 plan `Done` → **STOP** → 打印 `## Phase 3: iteration-close`。
|
|
77
29
|
|
|
78
|
-
|
|
79
|
-
if command -v mstar-harness >/dev/null 2>&1; then mstar-harness dispatch validate "<latest-assignment-file>" || exit 1; fi
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
> 路径必须加引号且替换为具体文件(如最新 `{SDD_DIR}/task-N-brief.md`,勿留尖括号)——agent 代入的路径不得进入 shell 无引号展开。
|
|
30
|
+
**Assignment preflight**:每次 implement/QC/QA 派发前按 **`mstar-iteration/references/command-shared-invariants.md`** 执行。
|
|
83
31
|
|
|
84
32
|
## Phase 3: iteration-close
|
|
85
33
|
|
|
86
|
-
当 **every** plan 为 `Done
|
|
87
|
-
|
|
88
|
-
1. **STOP** per-plan loop — 不得开 PR、不得进入 Phase 4/5、不得输出「迭代完成」摘要。
|
|
89
|
-
2. 打印标题 **`## Phase 3: iteration-close`**(用户可见的 phase 边界)。
|
|
90
|
-
3. Execute **`mstar-iteration` § Phase 3**(→ **`references/phase-3-iteration-close.md`** §3.0→§3.5):§3.0.5 compass 规范化 → §3.1 close entry checklist(**HARD GATE**,必须打印)→ §3.2 compound + **`<iteration-id>/` package promotion**(`mstar-compound` Phase 6 索引登记)→ §3.3 roadmap → §3.4 frontmatter `status: completed` + `end_date` → §3.5 close exit checklist + commit → push。
|
|
91
|
-
4. §3.5 exit 全 `[x]` 且 compass `status: completed` 后 → 打印 **`## Phase 4: Create PR`**。
|
|
92
|
-
|
|
93
|
-
**Phase 3 exit(非 iteration-drive Done)**: §3.5 checklist `[x]` + compass frontmatter `completed`。
|
|
34
|
+
当 **every** plan 为 `Done`:**STOP** per-plan loop → 打印 **`## Phase 3: iteration-close`** → execute **`mstar-iteration` § Phase 3**(→ **`references/phase-3-iteration-close.md`** §3.0→§3.5;§3.1 entry checklist HARD GATE;§3.2 compound + package promotion;§3.4 `status: completed` + `end_date`)→ §3.5 exit 全 `[x]` 后打印 **`## Phase 4: Create PR`**。
|
|
94
35
|
|
|
95
36
|
## Phase 4: Create PR
|
|
96
37
|
|
|
97
|
-
Execute **`mstar-iteration` § Phase 4**(→ **`references/phase-4-5-pr-delivery.md`**):打印 `## Phase 4: Create PR` → resolve `metadata.target_branch
|
|
98
|
-
|
|
99
|
-
**Phase 4 exit(非 iteration-drive Done)**: PR 已创建且 head = `spec_integration_branch`。
|
|
38
|
+
Execute **`mstar-iteration` § Phase 4**(→ **`references/phase-4-5-pr-delivery.md`**):打印 `## Phase 4: Create PR` → resolve `metadata.target_branch`(缺失 → **STOP**,never default `main`/`master`)→ 创建 PR `spec_integration_branch` → `target_branch` → 记录 PR URL + number → 勾掉 `phase-4-create-pr` → **Immediately** 打印 **`## Phase 5: PR merge-ready`**(**禁止**在此停止或汇报 Done)。
|
|
100
39
|
|
|
101
40
|
## Phase 5: PR merge-ready(babysit loop)
|
|
102
41
|
|
|
103
|
-
Execute **`mstar-iteration` § Phase 5**(→ **`references/phase-4-5-pr-delivery.md`** §5.0–§5.2;**§5.1a push cadence HARD**)。**§5.5 exit 全 `[x]` =
|
|
104
|
-
|
|
105
|
-
本 command **叠加**可选 helper skill 发现(**non-`mstar-*`**;不写入 `mstar-*` load order):
|
|
106
|
-
|
|
107
|
-
### 5.0 Discover optional helper skills
|
|
108
|
-
|
|
109
|
-
Helper 搜索路径 → **`mstar-iteration/references/phase5-helper-discovery.md`**(babysit / `*-babysit` / greploop 路径清单;first readable `SKILL.md` wins per name)。
|
|
110
|
-
|
|
111
|
-
| Priority | Condition | Read before loop | Primary done signal |
|
|
112
|
-
|----------|-----------|------------------|---------------------|
|
|
113
|
-
| 1 | `babysit` **or** any `*-babysit` found | that skill’s `SKILL.md`(prefer exact `babysit`, else first matching `*-babysit`) | Required CI **all green** + **all** review threads **resolved** |
|
|
114
|
-
| 2 | `greploop` found **and** repo has Greptile/greploop | `greploop` SKILL.md | Greptile score **5/5** on this PR(**additive** — does not replace priority-1 gates) |
|
|
115
|
-
| 3 | else neither babysit/`*-babysit` | —(fallback = babysit 同级 CI + reviews 门禁) | Required CI **all green** + **all** review threads **resolved** |
|
|
116
|
-
|
|
117
|
-
**Both babysit/`*-babysit` and greploop apply**: run **babysit/`*-babysit` first**(CI + reviews),then optional greploop until Greptile **5/5**(串行;§5.5 仍须满足全部 exit 项)。Do **not** prefer greploop over babysit。**No greploop / repo without Greptile**: skip greploop entirely。**All modes** share §5.5 exit checklist(Greptile 5/5 only when greploop mode ran or repo shows a Greptile score)。
|
|
118
|
-
|
|
119
|
-
### 5.1 Loop + review fix hygiene(all modes)
|
|
120
|
-
|
|
121
|
-
Execute **`mstar-iteration` §5.1 loop**(`references/phase-4-5-pr-delivery.md` §5.1):status(`gh pr view <number> --json mergeable,mergeStateStatus,statusCheckRollup,reviewDecision` 或宿主等价 API)→ merge conflicts → unresolved reviews → CI → **§5.1a idle push**(一批修复一次 push)→ mode-specific pass(babysit SKILL / greploop / fallback)→ §5.1 review fix hygiene(**comment on same thread + resolve**)→ repeat until §5.5。Fixes **push 到 `spec_integration_branch`**(PR head);禁止另开分支替代。**Checkout(HARD,§5.0)**:直接在 control / 集成分支 checkout 上修;**禁止**另开 Phase 5 fix worktree,**禁止**套用 Phase 2 control 产品编辑禁令。产品代码修复 → **dispatch** dev/ops(Assignment cwd = control);PM 线程不代写。**禁止**为「让 CI 变绿」而改 workflow,除非用户明确授权。多轮仍 blocked → 升级用户。
|
|
122
|
-
|
|
123
|
-
### 5.2 Phase 5 exit checklist(iteration-drive Done)
|
|
42
|
+
Execute **`mstar-iteration` § Phase 5**(→ **`references/phase-4-5-pr-delivery.md`** §5.0–§5.2;**§5.1a push cadence HARD**)。**§5.5 exit checklist 全 `[x]` = 本 command Done.**
|
|
124
43
|
|
|
125
|
-
|
|
44
|
+
本 command **叠加**可选 helper skill 发现(**non-`mstar-*`**;不写入 `mstar-*` load order)→ **`mstar-iteration/references/phase5-helper-discovery.md`**(babysit / `*-babysit` / greploop / fallback 路径清单;first readable `SKILL.md` wins)。Loop + review fix hygiene + exit checklist → §5.1–§5.2(同上 reference)。
|
|
126
45
|
|
|
127
46
|
**Then** report: iteration id, plans completed, compound summary, PR link, merge-ready evidence(CI snapshot + review resolution + Greptile if applicable)。
|
|
128
47
|
|