@mstar-harness/engine 0.0.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/dist/path.d.ts ADDED
@@ -0,0 +1,117 @@
1
+ import { type ValidationResult } from "./core.js";
2
+ /**
3
+ * Options for `resolveHarnessDir`.
4
+ */
5
+ export type ResolveHarnessDirOptions = {
6
+ /**
7
+ * Explicit harness root. Resolved against `startDir` when relative.
8
+ * Takes precedence over `MSTAR_HARNESS_DIR` and over default probing.
9
+ * Authoritative: the path is returned even when it does not exist yet
10
+ * (the caller may scaffold it).
11
+ */
12
+ harnessDir?: string;
13
+ };
14
+ /**
15
+ * Resolve `{HARNESS_DIR}` per plan-conventions § {HARNESS_DIR} 解析顺序
16
+ * (find-first-stop): `.mstar/` → `.agents/` → `.plans/`/`plans/`, walking up
17
+ * from `startDir`. Harness candidates are dir-existence (the empty-dir rule
18
+ * applies to `{SPECS_DIR}` only). An explicit override via `opts.harnessDir`
19
+ * or `MSTAR_HARNESS_DIR` wins over probing.
20
+ *
21
+ * Returns the absolute harness dir, or `null` when no candidate exists.
22
+ */
23
+ export declare function resolveHarnessDir(startDir?: string, opts?: ResolveHarnessDirOptions): string | null;
24
+ /**
25
+ * Options for `resolveSpecsDir`.
26
+ */
27
+ export type ResolveSpecsDirOptions = {
28
+ /**
29
+ * Default true: when every candidate is absent or empty, create
30
+ * `{HARNESS_DIR}/specs/` (plan-conventions § 创建默认). Read-only callers
31
+ * (e.g. `mstar path resolve`) pass `false` to skip the side effect.
32
+ */
33
+ create?: boolean;
34
+ };
35
+ /**
36
+ * Resolve `{SPECS_DIR}` per plan-conventions § {SPECS_DIR} 解析: first
37
+ * non-empty candidate wins — `{HARNESS_DIR}/specs/` → `docs/specs/` →
38
+ * repo-root `specs/` (repo root = parent of the harness dir), then the
39
+ * legacy read-only `designs/` candidates (`{HARNESS_DIR}/designs/` →
40
+ * repo-root `designs/`, § {SPECS_DIR} 解析 Legacy — 兼容读 only, never
41
+ * created by init). A candidate that exists but holds no files is treated
42
+ * as absent (empty-dir rule, recursive). When all candidates are absent,
43
+ * `{HARNESS_DIR}/specs/` is created and returned (unless `create: false`).
44
+ */
45
+ export declare function resolveSpecsDir(harnessDir: string, opts?: ResolveSpecsDirOptions): string;
46
+ /**
47
+ * Compose `{PLAN_DIR}` from the harness dir (plan-conventions § 路径符号).
48
+ * Legacy layout: when the harness root is a plans dir itself (`.plans/` or
49
+ * `plans/`, resolution rung 3), `{HARNESS_DIR}={PLAN_DIR}` — the same
50
+ * directory is returned.
51
+ */
52
+ export declare function resolvePlanDir(harnessDir: string): string;
53
+ /**
54
+ * Single safe path component for per-plan path composition
55
+ * (qc2 F-001 — path traversal guard): rejects `""`, `.`, `..`, and any
56
+ * `/` or `\`; allows `[A-Za-z0-9._-]+` only. Throws with a clear message so
57
+ * callers interpolating a plan id into a path (archive files, SDD dirs)
58
+ * can never escape the intended parent directory.
59
+ */
60
+ export declare function assertSafePathComponent(value: string, what: string): void;
61
+ /**
62
+ * Compose `{SDD_DIR}` = `{HARNESS_DIR}/sdd/<plan-id>/` (plan-conventions
63
+ * § 路径符号). The per-plan directory is created by the sdd workspace flow,
64
+ * not here. `planId` must be a single safe path component (traversal
65
+ * guard) — see `assertSafePathComponent`.
66
+ */
67
+ export declare function resolveSddDir(harnessDir: string, planId: string): string;
68
+ /**
69
+ * Compose `{ITERATION_DIR}` = `{HARNESS_DIR}/iterations/` (plan-conventions
70
+ * § 路径符号).
71
+ */
72
+ export declare function resolveIterationDir(harnessDir: string): string;
73
+ /**
74
+ * Initialize the harness directory under `root`: create `.mstar/` with
75
+ * `plans/`, `iterations/`, `knowledge/`, `specs/`, `sdd/` and write
76
+ * `status.json` from the empty template (plan-conventions § 初始化 Plan 目录).
77
+ * Idempotent: an existing non-empty `status.json` is never clobbered.
78
+ * Returns the absolute harness dir.
79
+ */
80
+ export declare function scaffoldHarness(root: string): string;
81
+ /**
82
+ * Harness kind for the gitignore fence — the canonical snippet is per
83
+ * harness layout (plan-conventions § Git 跟踪策略): `.mstar/` (default) and
84
+ * legacy `.agents/`.
85
+ */
86
+ export type HarnessKind = "mstar" | "agents";
87
+ /**
88
+ * Emit the canonical `.gitignore` snippet for `kind` (plan-conventions
89
+ * § Git 跟踪策略): the process-artifact ignore set (`archived/`,
90
+ * `iterations/`, `plans/`, `sdd/`, `notes.json`, `status.json` under the
91
+ * harness dir) with the tracked/results note. When the kind is unknown
92
+ * (omitted), both snippets are emitted so either fence can be applied.
93
+ */
94
+ export declare function emitGitignoreSnippet(kind?: HarnessKind): string;
95
+ /**
96
+ * Validate that `<root>/.gitignore` contains a complete canonical
97
+ * process-artifact ignore set (plan-conventions § Git 跟踪策略). Rule
98
+ * (chosen alignment): the gate passes when the repo's .gitignore holds ONE
99
+ * complete set for the DETECTED harness kind — `.mstar/` for a `.mstar`
100
+ * harness, `.agents/` for a legacy `.agents` harness; layouts without a
101
+ * canonical snippet (rung-3 `.plans`/`plans`, or no harness yet) accept
102
+ * either complete set. This is deliberately per-kind, unlike the CLI `init`
103
+ * fence which requires BOTH prefixes (flat dual-entry list — packages/cli
104
+ * src/adapters/shared-install.ts HARNESS_PROCESS_GITIGNORE); a repo fenced
105
+ * for one layout still passes here. Extra entries are fine; any missing
106
+ * entry of the required set is a violation. Non-blocking: returns a
107
+ * `ValidationResult` (v1 enforcement depth, roadmap §8.5).
108
+ */
109
+ export declare function validateGitignore(root: string): ValidationResult;
110
+ /**
111
+ * Plan-writing path gate (plan-conventions § Plan-Writing Path Gate +
112
+ * harness-core 护栏): plans must live under `{PLAN_DIR}`; external default
113
+ * plan directories are rejected. When `harnessDir` is `null` (persistent
114
+ * plan tracking not enabled) any plan path is rejected with a fix pointing
115
+ * at `scaffoldHarness`. Non-blocking: returns a `ValidationResult`.
116
+ */
117
+ export declare function assertPlanWritingPath(planPath: string, harnessDir: string | null): ValidationResult;
@@ -0,0 +1,87 @@
1
+ import type { GateResult } from "./core.js";
2
+ /** One row of the role reference mapping (mstar-roles § Role Reference
3
+ * Mapping): `agentId` → skill-relative reference file. */
4
+ export type RoleMappingEntry = {
5
+ agentId: string;
6
+ reference: string;
7
+ };
8
+ /** The 13 role ids → `references/<role>.md` mapping, embedded as data
9
+ * (mstar-roles § Role Reference Mapping; shared families point at the
10
+ * shared reference files). */
11
+ export declare const ROLE_MAPPING: readonly RoleMappingEntry[];
12
+ /** A role family that MUST resolve to one shared reference file
13
+ * (mstar-roles § Maintenance Rules: "Keep shared-family roles
14
+ * (`fullstack-dev*`, `qc-specialist*`) on one shared reference file"). */
15
+ export type RoleFamily = {
16
+ family: string;
17
+ memberIds: readonly string[];
18
+ };
19
+ export declare const SHARED_FAMILIES: readonly RoleFamily[];
20
+ /** Dev-track parameter row (mstar-roles § Parameter Table (SSOT) — dev
21
+ * track): `primary` (backend-led) or `parallel_secondary` (second track). */
22
+ export type DevTrackParam = {
23
+ roleId: string;
24
+ track: "primary" | "parallel_secondary";
25
+ };
26
+ export declare const DEV_TRACK_PARAMS: readonly DevTrackParam[];
27
+ /** QC reviewer parameter row (mstar-roles § Parameter Table (SSOT) — QC
28
+ * reviewer): seat index, review focus, and the `qc<index>` report suffix
29
+ * that lands at `{SDD_DIR}/review/qc<index>.md`. */
30
+ export type QcReviewerParam = {
31
+ roleId: string;
32
+ reviewerIndex: number;
33
+ focus: string;
34
+ reportSuffix: string;
35
+ };
36
+ export declare const QC_REVIEWER_PARAMS: readonly QcReviewerParam[];
37
+ /** Override point for tests / future role-table extensions; every slot
38
+ * defaults to the embedded SSOT tables above. */
39
+ export type RoleMappingOptions = {
40
+ mapping?: readonly RoleMappingEntry[];
41
+ families?: readonly RoleFamily[];
42
+ devTrack?: readonly DevTrackParam[];
43
+ qcReviewers?: readonly QcReviewerParam[];
44
+ };
45
+ /**
46
+ * Validate the role mapping + parameter tables against the on-disk skill
47
+ * layout (mstar-roles § Role Reference Mapping / § Parameter Table (SSOT)
48
+ * / § Maintenance Rules):
49
+ * - every mapped agent id resolves to `references/<role>.md` under
50
+ * `rolesDir`;
51
+ * - shared families (`fullstack-dev*`, `qc-specialist*`) resolve to ONE
52
+ * shared reference file each;
53
+ * - every parameter row references a mapped role, exactly once;
54
+ * - dev track values are `primary` / `parallel_secondary`;
55
+ * - the QC parameter table contract holds: reviewer_index is exactly
56
+ * {1, 2, 3} across the three seats, each seat has a focus, and
57
+ * `report_suffix === qc<reviewer_index>`.
58
+ *
59
+ * Violations:
60
+ * - `roles.mapping.reference.missing` — mapped reference file not on disk
61
+ * - `roles.mapping.family.member.missing` — family member absent from mapping
62
+ * - `roles.mapping.family.shared` — family members resolve to different files
63
+ * - `roles.param.role.missing` — parameter row names an unmapped role
64
+ * - `roles.param.role.duplicate` — role appears in two parameter rows
65
+ * - `roles.param.track` — dev track value not primary/parallel_secondary
66
+ * - `roles.param.qc.index.set` — reviewer_index set ≠ {1, 2, 3} (high)
67
+ * - `roles.param.qc.focus.missing` — QC seat without a focus
68
+ * - `roles.param.qc.suffix` — report_suffix ≠ qc<reviewer_index>
69
+ */
70
+ export declare function validateRoleMapping(rolesDir: string, options?: RoleMappingOptions): GateResult;
71
+ /**
72
+ * Lint load-order declarations across skill texts (mstar-harness-core
73
+ * § 加载约定: every `mstar-*` topic skill presumes the reader has Read core
74
+ * first, so each must declare `mstar-harness-core` in its Load Order /
75
+ * First action section).
76
+ *
77
+ * Input: `skillTexts` maps skill name → full SKILL.md text. `mstar-harness-
78
+ * core` itself and non-`mstar-*` skills are exempt. Heuristic: a section
79
+ * headed Load Order / Load order / First action must exist and mention
80
+ * `mstar-harness-core` inside that section (mentions in later sections do
81
+ * not count).
82
+ *
83
+ * Violations:
84
+ * - `roles.loadorder.section.missing` — no Load Order / First action section
85
+ * - `roles.loadorder.core.missing` — section exists without the core mention
86
+ */
87
+ export declare function lintLoadOrder(skillTexts: Record<string, string>): GateResult;
package/dist/sdd.d.ts ADDED
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Error carrying the ported script exit code so the CLI can map validation
3
+ * failures to identical non-zero exits.
4
+ */
5
+ export declare class SddScriptError extends Error {
6
+ readonly exitCode: number;
7
+ constructor(message: string, exitCode: number);
8
+ }
9
+ /**
10
+ * Options for `sddWorkspace` — `mstar sdd workspace PLAN_ID [CONTROL_ROOT]`
11
+ * usage plus the harness-root override (plan finding 2026-08-08).
12
+ */
13
+ export type SddWorkspaceOptions = {
14
+ /** Control worktree repo root — CLI 2nd arg / `MSTAR_CONTROL_ROOT`. */
15
+ controlRoot?: string;
16
+ /** Explicit harness root — `MSTAR_HARNESS_DIR` / `--harness-dir`. */
17
+ harnessDir?: string;
18
+ /** Working directory for git probes; default `process.cwd()`. */
19
+ cwd?: string;
20
+ };
21
+ /** Options for `taskBrief` (mirrors `$SDD_DIR` for the default out path). */
22
+ export type TaskBriefOptions = {
23
+ sddDir?: string;
24
+ };
25
+ /** Options for `reviewPackage` (mirrors `$SDD_DIR` + git probe cwd). */
26
+ export type ReviewPackageOptions = {
27
+ sddDir?: string;
28
+ cwd?: string;
29
+ };
30
+ /**
31
+ * Resolve and ensure `{SDD_DIR}` = `{HARNESS_DIR}/sdd/<plan-id>/` (prints
32
+ * the absolute path). Resolution order:
33
+ *
34
+ * 1. fail-closed FIRST: a linked worktree without a control root never
35
+ * resolves or creates any SDD tree under the feature checkout (refuses a
36
+ * second SDD tree; no override or probe may bypass this guard);
37
+ * 2. explicit harness-root override (`opts.harnessDir` / `MSTAR_HARNESS_DIR`)
38
+ * — plan finding 2026-08-08: covers `.harness`-rooted repos the
39
+ * status.json probe misses; resolved relative to the established root;
40
+ * 3. `status.json` probe at root (`.mstar` → `.agents`);
41
+ * 4. fallback: existing `.mstar`/`.agents` dir, else `.mstar`.
42
+ *
43
+ * `controlRoot` (CLI 2nd arg / `MSTAR_CONTROL_ROOT`) pins `root` to the
44
+ * control worktree instead of the cwd's git top-level.
45
+ */
46
+ export declare function sddWorkspace(planId: string, opts?: SddWorkspaceOptions): string;
47
+ /**
48
+ * Extract the `## Task N` section of a plan into a file (default
49
+ * `{SDD_DIR}/task-N-brief.md`). Line state machine: ``` fences toggle
50
+ * `infence`; headings inside fences are ignored; printing starts at the
51
+ * heading for `taskN` and continues until the NEXT `## Task` heading (or
52
+ * EOF for the last task) — a later Task heading resets the section. A
53
+ * missing task writes an empty file then fails with exit-3
54
+ * (`SddScriptError.exitCode === 3`).
55
+ */
56
+ export declare function taskBrief(planFile: string, taskN: number, outFile?: string, opts?: TaskBriefOptions): string;
57
+ /**
58
+ * Write commit list, stat summary and `git diff -U10` for `BASE..HEAD`
59
+ * into a file (default `{SDD_DIR}/review-<short base>..<short head>.diff`).
60
+ * Both refs are validated with `git rev-parse --verify --quiet` (any ref
61
+ * the original accepted is accepted here; the SHA-only guard is
62
+ * `assertBaseSha`).
63
+ */
64
+ export declare function reviewPackage(base: string, head: string, outFile?: string, opts?: ReviewPackageOptions): string;
65
+ /**
66
+ * BASE_SHA guard (mstar-sdd SKILL.md red flags: never use `HEAD~1` as the
67
+ * review BASE — multi-commit tasks truncate). Accepts only a full or prefix
68
+ * commit SHA that exists in the repo; throws `SddScriptError` (exit 2)
69
+ * otherwise.
70
+ */
71
+ export declare function assertBaseSha(ref: string, opts?: {
72
+ cwd?: string;
73
+ }): void;
74
+ /**
75
+ * True when `{sddDir}/task-N-report.md` exists and is non-empty
76
+ * (file-handoffs.md: the implementer writes a full report to
77
+ * `task-N-report.md`; an empty file carries no evidence).
78
+ */
79
+ export declare function taskReportExists(sddDir: string, taskN: number): boolean;
80
+ /**
81
+ * Read the progress ledger (mstar-sdd SKILL.md § Progress ledger) as
82
+ * non-empty trimmed lines; missing `progress.md` reads as `[]`. Tasks
83
+ * marked `Task N: complete` are DONE and must not be re-dispatched.
84
+ */
85
+ export declare function readProgressLedger(sddDir: string): string[];
86
+ /**
87
+ * Sticky implementer session ledger — `{SDD_DIR}/implementer-session.json`
88
+ * (sticky-implementer-session.md § Session ledger).
89
+ */
90
+ export type ImplementerSessionLedger = {
91
+ plan_id: string;
92
+ execute_as: string;
93
+ session_mode: "sticky" | "fresh";
94
+ host: string;
95
+ /** Agent id from the first Task return — required for resume. */
96
+ host_agent_id?: string;
97
+ working_branch: string;
98
+ started_task: number;
99
+ last_task: number;
100
+ started_at: string;
101
+ };
102
+ /** Input to `implementerSessionStickyRules`. */
103
+ export type StickyRulesInput = {
104
+ session: ImplementerSessionLedger;
105
+ /** Task about to be dispatched. */
106
+ nextTask: number;
107
+ /** Tasks covered by this dispatch (micro-batch); default 1. */
108
+ microBatchTasks?: number;
109
+ };
110
+ /** Verdict of the sticky resume rules. */
111
+ export type StickyRulesResult = {
112
+ resume: boolean;
113
+ reason: string;
114
+ };
115
+ /**
116
+ * Sticky resume rules (sticky-implementer-session.md + SKILL.md red flag
117
+ * "Resume implementer without host_agent_id"): a sticky session may only
118
+ * resume when `session_mode` is `sticky`, `host_agent_id` is present,
119
+ * `nextTask` is not already completed through `last_task`, and the
120
+ * micro-batch size is ≤ 3 (max without user override). Reviewers never
121
+ * resume — that rule lives in the PM flow, not the session ledger.
122
+ */
123
+ export declare function implementerSessionStickyRules(input: StickyRulesInput): StickyRulesResult;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Engine skill-authoring module — frontmatter lint, 5-question body lint,
3
+ * and skill-relative asset-path resolution (thin; roadmap §8.2
4
+ * `skill-authoring` row, §4.5).
5
+ *
6
+ * Source skills (semantic SSOT — this module implements their deterministic
7
+ * rules, it never redefines them; roadmap §8.5 C2):
8
+ * - `mstar-skill-authoring` SKILL.md § Frontmatter Contract — `name` stable
9
+ * lowercase-hyphen; `description` is the trigger contract (not a workflow
10
+ * summary), third person.
11
+ * - `mstar-skill-authoring` SKILL.md § Body 必须回答的 5 问 + § 默认 Body
12
+ * 结构 — a SKILL.md body answers five questions via key sections (Load
13
+ * Order, Workflow, Decision Rules, Evidence, References).
14
+ * - `mstar-skill-authoring` SKILL.md § Skill-relative script and asset
15
+ * paths ("skill `my-skill` → scripts/do-thing") + `mstar-host` SKILL.md
16
+ * § Resolve loaded skill root (per-host resolution).
17
+ *
18
+ * The SkillsBench six principles and trigger-contract reasoning stay prompt.
19
+ */
20
+ import type { GateResult } from "./core.js";
21
+ import { type HostId } from "./host.js";
22
+ /**
23
+ * Lint a skill file's frontmatter (mstar-skill-authoring § Frontmatter
24
+ * Contract): `name` lowercase-hyphen, `description` present / third-person /
25
+ * not a workflow summary. Re-exports `lint.lintSkillFrontmatter` — the
26
+ * single parser/heuristics implementation (one source of truth; the CLI
27
+ * `mstar skill lint` calls this alias). Violations keep the `lint.frontmatter.*`
28
+ * codes.
29
+ */
30
+ export { lintSkillFrontmatter as lintFrontmatter } from "./lint.js";
31
+ /** One of the five body questions and the canonical section that answers it
32
+ * (mstar-skill-authoring § Body 必须回答的 5 问 / § 默认 Body 结构). */
33
+ export type FiveQuestionSection = {
34
+ key: string;
35
+ label: string;
36
+ question: string;
37
+ };
38
+ export declare const FIVE_QUESTION_SECTIONS: readonly FiveQuestionSection[];
39
+ /**
40
+ * Lint a SKILL.md body for the 5-question contract (mstar-skill-authoring
41
+ * § Body 必须回答的 5 问). Heuristic: each of the five questions must be
42
+ * answered by the presence of its canonical section heading (case-
43
+ * insensitive substring match on heading text, any heading level — so
44
+ * "## Load Order (Required)" and "### Workflow — main path" both match).
45
+ * Content judgment (whether the answer is actually narrow / procedural)
46
+ * stays prompt. Advisory: violations are `low` severity (v1 non-blocking).
47
+ *
48
+ * Violations: `skill-authoring.five-question.<key>` for each uncovered
49
+ * question.
50
+ */
51
+ export declare function lintFiveQuestion(bodyText: string): GateResult;
52
+ /**
53
+ * Resolve a skill-relative asset path (mstar-skill-authoring § Skill-
54
+ * relative script and asset paths: name assets as skill `<name>` →
55
+ * `scripts/…` / `references/…`; never a literal `skills/<name>/…` path from
56
+ * a consumer cwd) into the host-specific resolution instruction
57
+ * (mstar-host § Resolve loaded skill root). No filesystem access — this is
58
+ * an instruction string an agent reads to find the asset.
59
+ */
60
+ export declare function resolveAssetPath(skillName: string, relPath: string, host: HostId): string;
@@ -0,0 +1,168 @@
1
+ import { type GateResult } from "./core.js";
2
+ import { type EnforcementFlag } from "./dispatch.js";
3
+ /**
4
+ * Loose shape of a parsed status.json document. All fields are `unknown`
5
+ * because documents come from JSON at runtime; validators narrow them.
6
+ */
7
+ export type StatusDoc = {
8
+ version?: unknown;
9
+ updated_at?: unknown;
10
+ plans?: unknown;
11
+ residual_findings?: unknown;
12
+ metadata?: unknown;
13
+ [key: string]: unknown;
14
+ };
15
+ /** Residual entry as parsed from status.json (loose — validated by `validateResidual`). */
16
+ export type ResidualEntry = {
17
+ id?: unknown;
18
+ title?: unknown;
19
+ severity?: unknown;
20
+ source?: unknown;
21
+ scope?: unknown;
22
+ decision?: unknown;
23
+ owner?: unknown;
24
+ target?: unknown;
25
+ tracking?: unknown;
26
+ detail_doc?: unknown;
27
+ lifecycle?: unknown;
28
+ closed_at?: unknown;
29
+ closure_note?: unknown;
30
+ closure_evidence?: unknown;
31
+ superseded_by?: unknown;
32
+ [key: string]: unknown;
33
+ };
34
+ /** Plan row as parsed from status.json (loose — validated by `validatePlanRow`). */
35
+ export type PlanRow = {
36
+ id?: unknown;
37
+ plan_id?: unknown;
38
+ title?: unknown;
39
+ file?: unknown;
40
+ status?: unknown;
41
+ metadata?: unknown;
42
+ execution_lease?: unknown;
43
+ [key: string]: unknown;
44
+ };
45
+ /** Findings cleanup policy mirror of Assignment `Findings cleanup`. */
46
+ export type FindingsCleanupMode = "zero-residual" | "allow-residual";
47
+ /** Computed rollup aggregates (jq semantics). */
48
+ export type TechDebtSummary = {
49
+ total_open: number;
50
+ by_severity: Record<string, number>;
51
+ by_target: Record<string, number>;
52
+ by_plan: Record<string, number>;
53
+ };
54
+ export type TechDebtCheck = {
55
+ field: "total_open" | "by_severity" | "by_target" | "by_plan";
56
+ status: "PASS" | "DRIFT";
57
+ };
58
+ /** Result of the rollup + drift check vs stored `metadata.tech_debt_summary`. */
59
+ export type TechDebtRollup = {
60
+ computed: TechDebtSummary;
61
+ stored: Record<string, unknown> | null;
62
+ checks: TechDebtCheck[];
63
+ overall: "PASS" | "DRIFT";
64
+ };
65
+ export type ArchiveResult = {
66
+ planId: string;
67
+ archived: number;
68
+ archivePath: string;
69
+ };
70
+ /**
71
+ * Normalize a residual `severity` value for reading/rolling up
72
+ * (status-and-residuals.md § severity 5 + rollup `norm_sev`):
73
+ * legacy `"warning"` → `"low"`; `null`/`""` → `"medium"`; anything else passes
74
+ * through unchanged (unknown values match no enum bucket, same as jq).
75
+ */
76
+ export declare function normalizeSeverity(value: unknown): unknown;
77
+ /**
78
+ * jq semantics: an entry is open when `.lifecycle // "open"` equals `"open"`
79
+ * (rollup `is_open`; status-and-residuals.md § lifecycle).
80
+ * The jq alternative operator `//` yields the default for `false` AND
81
+ * `null` (not just null) — `lifecycle: false` therefore counts as open.
82
+ * Exported for consumers that need the shared open semantics (e.g. the
83
+ * iteration phase-gate entry check) instead of a local re-implementation.
84
+ */
85
+ export declare function isOpenResidual(entry: Record<string, unknown>): boolean;
86
+ /**
87
+ * Validate one `plans[]` row (status-and-residuals.md § Basic structure +
88
+ * § Compatibility: read accepts `id` or `plan_id`; write one canonical key).
89
+ * Required: `id` (or legacy `plan_id`), `title`, `file`, `status` (one of
90
+ * Todo|InProgress|InReview|Blocked|Done); `metadata` optional but must be an
91
+ * object when present. `execution_lease` is type-checked here; the full lease
92
+ * state machine lives in the lease module (Task 5).
93
+ */
94
+ export declare function validatePlanRow(row: unknown): GateResult;
95
+ /**
96
+ * Validate one residual entry (status-and-residuals.md § Basic structure):
97
+ * required `id`, `title`, `severity`, `source`, `scope`, `decision`, `owner`,
98
+ * `target`, `tracking`; `severity` from the enum (legacy `"warning"` is read
99
+ * but forbidden on new entries — flagged with fix); `decision` from
100
+ * defer|accept|risk-accepted; `lifecycle` optional from the close enum.
101
+ */
102
+ export declare function validateResidual(entry: unknown): GateResult;
103
+ /**
104
+ * Validate a status.json document (schema: status-and-residuals.md
105
+ * § Basic structure + § General constraints). Accepts a parsed document or a
106
+ * file path (malformed JSON yields a `status.invalid-json` violation, never a
107
+ * throw). Required top-level fields: `version`, `updated_at`, `plans[]`,
108
+ * root-only `residual_findings`, `metadata`. Root-only canonical: any
109
+ * `metadata.residual_findings` key is flagged as dual-write.
110
+ */
111
+ export declare function validateStatus(docOrPath: StatusDoc | string): GateResult;
112
+ /**
113
+ * Archive the open residuals of a plan (status-and-residuals.md
114
+ * § Residual findings lifecycle): append every entry of
115
+ * `residual_findings[<plan-id>]` to `{HARNESS_DIR}/archived/residuals/
116
+ * <plan-id>.json` (stamped `archived_at`), delete the key from the open list,
117
+ * and bump root `updated_at`. No-op (archived 0) when the plan has no open
118
+ * residuals. `harnessDir` defaults to the resolved `{HARNESS_DIR}` from cwd.
119
+ *
120
+ * `planId` is validated as a single safe path component before it is used to
121
+ * build the archive path (path traversal guard — see
122
+ * `assertSafePathComponent`). The status.json read-modify-write runs under
123
+ * `withStatusWriteLock` so concurrent coordination writers serialize.
124
+ *
125
+ * simplify: archive append + status.json update are two separate writes —
126
+ * a crash between them leaves entries both archived and open; re-running is
127
+ * safe because appends dedup on `entries[].id` (F-10). Not transactional by
128
+ * design (v1); the write lock from F-004 keeps concurrent writers safe.
129
+ */
130
+ export declare function archiveResiduals(planId: string, harnessDir?: string): Promise<ArchiveResult>;
131
+ /**
132
+ * Findings cleanup gate (status-and-residuals.md § Findings cleanup modes).
133
+ * `zero-residual`: only true blocker-defers (`decision: defer` + non-empty
134
+ * `target`) may stay open — fixable findings, `nit`s, and waived/
135
+ * risk-accepted entries are violations. `allow-residual` (default): open
136
+ * residuals are fine unless an unresolved Critical remains. Mode resolution:
137
+ * explicit `opts.mode` → `plans[].metadata.findings_cleanup` → `allow-residual`.
138
+ */
139
+ export declare function findingsCleanupGate(doc: StatusDoc, planId: string, opts?: {
140
+ mode?: FindingsCleanupMode;
141
+ }): GateResult;
142
+ /**
143
+ * Resolve the repo-level hard-enforcement flag from the iteration compass
144
+ * (roadmap §8.5 C4/D2): `{ITERATION_DIR}/<id>/delivery-compass.md` files are
145
+ * scanned; only compasses still steering the repo count — frontmatter
146
+ * `status: active` or `status: locked` — and the FIRST such compass whose
147
+ * frontmatter declares `enforcement: hard` hardens the gate in this repo.
148
+ * A COMPLETED (or status-less/archived) iteration's compass NEVER hardens:
149
+ * D2 rollback = unset the flag in the ACTIVE compass, and that must work
150
+ * while older completed compasses still declare hard (qc1 F-001 / qc2 F-002).
151
+ * A counting compass declaring a non-hard value, or no compass at all,
152
+ * leaves the flag unset (`source: none`) — hard gates are never the default
153
+ * and the flag is inert when the engine is absent. Frontmatter is
154
+ * `---`-fenced; hard declarations in the compass BODY do not count (the
155
+ * frontmatter is the schema surface — see iteration.compassSchema).
156
+ */
157
+ export declare function resolveCompassEnforcement(harnessDir: string): EnforcementFlag;
158
+ /**
159
+ * Compute the `metadata.tech_debt_summary` rollup (status-and-residuals.md
160
+ * § `metadata.tech_debt_summary`): `total_open` / `by_severity` /
161
+ * `by_target` / `by_plan` over open entries of root `residual_findings`
162
+ * merged with the legacy `metadata.residual_findings` read path (canonical
163
+ * keys win; legacy `"warning"` → `low`, `null`/`""` → `medium`; closed
164
+ * entries skipped; missing `target` groups under `"unspecified"`), then
165
+ * compare field-by-field against stored `metadata.tech_debt_summary`.
166
+ * Accepts a parsed document or a file path. Does not write status.json.
167
+ */
168
+ export declare function techDebtRollup(docOrPath: StatusDoc | string): TechDebtRollup;
@@ -0,0 +1,104 @@
1
+ import type { GateResult } from "./core.js";
2
+ /** One L2 parallel implement track (mstar-branch-worktree L2 table). */
3
+ export type WorktreeTrack = {
4
+ /** Absolute worktree checkout path for the track. */
5
+ worktreePath: string;
6
+ /** PM-approved Working branch checked out in that worktree. */
7
+ workingBranch: string;
8
+ };
9
+ /**
10
+ * L1 pre-dispatch checklist input — mirrors the status.json L1 fields
11
+ * (`metadata.control_worktree_path` + `plans[].execution_lease`).
12
+ */
13
+ export type L1PreDispatchInput = {
14
+ /** `metadata.control_worktree_path` — harness coordination SSOT checkout. */
15
+ controlWorktreePath: string;
16
+ /** `execution_lease.worktree_path` — the plan's feature worktree. */
17
+ leaseWorktreePath: string;
18
+ /** `execution_lease.working_branch` — the plan's Working branch. */
19
+ leaseWorkingBranch: string;
20
+ /** Plan id (`status.json.plans[].id` / `{SDD_DIR}` segment) — message context. */
21
+ planId: string;
22
+ };
23
+ /** L2 pre-dispatch checklist input — the plan's parallel writable tracks. */
24
+ export type L2PreDispatchInput = {
25
+ tracks: readonly WorktreeTrack[];
26
+ };
27
+ /** Git branch probe options — keep checks pure by precomputing probe inputs. */
28
+ export type BranchProbeOptions = {
29
+ /** git executable to invoke (default `git`). */
30
+ gitPath?: string;
31
+ /**
32
+ * Precomputed branch lookup keyed by absolute worktree path; return
33
+ * `undefined` to fall back to a real `git -C <path> branch --show-current`
34
+ * probe. Lets callers (tests, host hooks) inject probe inputs without a
35
+ * subprocess.
36
+ */
37
+ branchOf?: (worktreePath: string) => string | undefined;
38
+ /**
39
+ * Git probe timeout in ms (default 10s; `MSTAR_GIT_PROBE_TIMEOUT_MS` env
40
+ * overrides; per-call value wins). On timeout the probe fails closed into
41
+ * `branch-probe-failed` — never hangs, never guesses a branch (qc3 F-4).
42
+ */
43
+ timeoutMs?: number;
44
+ };
45
+ /**
46
+ * QC/QA alignment fields — `plan_id` + `Review range`/`Diff basis` must be
47
+ * byte-identical across the QC tri + QA assignments (逐字相同).
48
+ */
49
+ export type QcAlignmentAssignment = {
50
+ planId: string;
51
+ reviewRange: string;
52
+ diffBasis: string;
53
+ };
54
+ /** `singleReviewSnapshot` input — alignment fields plus a precomputed review HEAD. */
55
+ export type QcSnapshotAssignment = QcAlignmentAssignment & {
56
+ /** Precomputed review HEAD (full SHA preferred) for that assignment. */
57
+ head?: string;
58
+ };
59
+ /**
60
+ * L1 cross-plan pre-dispatch checklist (mstar-branch-worktree L1 table +
61
+ * Harness path SSOT hard rules): control path recorded, feature worktree
62
+ * exists, lease worktree ≠ control path, and the branch checked out at the
63
+ * feature worktree matches `execution_lease.working_branch`.
64
+ */
65
+ export declare function l1PreDispatchCheck(input: L1PreDispatchInput, opts?: BranchProbeOptions): GateResult;
66
+ /**
67
+ * L2 within-plan pre-dispatch checklist (parallel-writable-pre-dispatch.md):
68
+ * every parallel writable track's `worktreePath` must be absolute and
69
+ * distinct (one Worktree per track — N parallel invokes ≠ isolation), the
70
+ * worktree dir must exist, and `git -C <path> branch --show-current` must
71
+ * match its Working branch — before the first concurrent writable dispatch.
72
+ * Fewer than one track is itself a violation (the checklist needs something
73
+ * to verify).
74
+ */
75
+ export declare function l2PreDispatchCheck(input: L2PreDispatchInput, opts?: BranchProbeOptions): GateResult;
76
+ /**
77
+ * L1 hard rule (Harness path SSOT): `execution_lease.worktree_path` MUST
78
+ * differ from `metadata.control_worktree_path`. String equality on the two
79
+ * paths — canonical absolute paths are the caller's contract (the lease
80
+ * validator already requires `worktree_path` to be absolute).
81
+ */
82
+ export declare function assertControlVsFeaturePath(controlWorktreePath: string, featureWorktreePath: string): GateResult;
83
+ /**
84
+ * Assert the branch checked out at `worktreePath` matches `expectedBranch`
85
+ * (the Assignment Working branch). Probe = `git -C <path> branch
86
+ * --show-current`; precompute via `opts.branchOf` for purity. Fail-closed on
87
+ * probe errors and detached HEAD.
88
+ */
89
+ export declare function assertBranchAlignment(worktreePath: string, expectedBranch: string, opts?: BranchProbeOptions): GateResult;
90
+ /**
91
+ * QC/QA 对齐字段契约: `plan_id` + `Review range`/`Diff basis` must be
92
+ * byte-identical (逐字相同 — no trimming, no normalization) across every
93
+ * assignment in the set, so the QC tri + QA review the same plan/feature and
94
+ * the same diff range. One violation per field that differs.
95
+ */
96
+ export declare function assertQcAlignment(assignments: readonly QcAlignmentAssignment[]): GateResult;
97
+ /**
98
+ * Single review snapshot precondition (派 QC 前置条件): all reviewable
99
+ * commits must sit on ONE Working branch HEAD before the QC tri + QA are
100
+ * dispatched. `head` is precomputed per assignment (full SHA preferred);
101
+ * different heads → violation; a missing head cannot confirm the snapshot →
102
+ * violation (fail-closed).
103
+ */
104
+ export declare function singleReviewSnapshot(assignments: readonly QcSnapshotAssignment[]): GateResult;