@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/README.md +27 -0
- package/dist/audit.d.ts +99 -0
- package/dist/compound.d.ts +90 -0
- package/dist/core.d.ts +112 -0
- package/dist/design-md.d.ts +113 -0
- package/dist/dispatch.d.ts +205 -0
- package/dist/engine.js +3439 -0
- package/dist/host.d.ts +95 -0
- package/dist/index.d.ts +52 -0
- package/dist/iteration.d.ts +88 -0
- package/dist/lease.d.ts +193 -0
- package/dist/lint.d.ts +206 -0
- package/dist/path.d.ts +117 -0
- package/dist/roles.d.ts +87 -0
- package/dist/sdd.d.ts +123 -0
- package/dist/skill-authoring.d.ts +60 -0
- package/dist/status.d.ts +168 -0
- package/dist/worktree.d.ts +104 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# @mstar-harness/engine
|
|
2
|
+
|
|
3
|
+
Morning Star (启明星) harness engine — deterministic library for harness checks (version, path, status, lease, validation), shared by the installer CLI and the OpenCode plugin.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @mstar-harness/engine
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { readHarnessVersion } from "@mstar-harness/engine";
|
|
15
|
+
|
|
16
|
+
const version = readHarnessVersion(); // "1.8.8" — monorepo root package.json
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Scope
|
|
20
|
+
|
|
21
|
+
- Importable library only — **no `bin`**; the CLI (`@mstar-harness/cli`) wraps engine functions as thin `mstar …` subcommands.
|
|
22
|
+
- Dependencies: `node:*` only (zero external runtime deps — all validators hand-rolled; ajv pruned as phantom, zod removed 2026-08-08).
|
|
23
|
+
- Skill prose stays authoritative; engine exports are the machine-checkable mirror of the rules the `mstar-*` skills state.
|
|
24
|
+
|
|
25
|
+
## License
|
|
26
|
+
|
|
27
|
+
MIT — see [LICENSE](https://github.com/btspoony/mstar-harness/blob/main/LICENSE).
|
package/dist/audit.d.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { GateResult } from "./core.js";
|
|
2
|
+
/** Priority values (mstar-audit SKILL § Plan files Status block). */
|
|
3
|
+
export declare const AUDIT_PRIORITIES: readonly ["P1", "P2", "P3"];
|
|
4
|
+
export type AuditPriority = (typeof AUDIT_PRIORITIES)[number];
|
|
5
|
+
/** Effort values (Morning Star agent-oriented effort scale). */
|
|
6
|
+
export declare const AUDIT_EFFORTS: readonly ["XS", "S", "M", "L", "XL"];
|
|
7
|
+
export type AuditEffort = (typeof AUDIT_EFFORTS)[number];
|
|
8
|
+
/** Risk values. */
|
|
9
|
+
export declare const AUDIT_RISKS: readonly ["LOW", "MED", "HIGH"];
|
|
10
|
+
export type AuditRisk = (typeof AUDIT_RISKS)[number];
|
|
11
|
+
/** Category codes (finding-format.md § Category codes + SKILL Status block). */
|
|
12
|
+
export declare const AUDIT_CATEGORIES: readonly ["bug", "security", "perf", "tests", "tech-debt", "migration", "dx", "docs", "direction"];
|
|
13
|
+
export type AuditCategory = (typeof AUDIT_CATEGORIES)[number];
|
|
14
|
+
/**
|
|
15
|
+
* Validate the audit Status block(s) of a plan file against the field
|
|
16
|
+
* contract (mstar-audit SKILL § Plan files):
|
|
17
|
+
* - `Priority`: P1 | P2 | P3
|
|
18
|
+
* - `Effort`: XS | S | M | L | XL
|
|
19
|
+
* - `Risk`: LOW | MED | HIGH
|
|
20
|
+
* - `Depends on`: `none` or `plans/NNN-*.md` (the `*` is a literal
|
|
21
|
+
* wildcard form — the documented scaffolded scheme; concrete
|
|
22
|
+
* `plans/NNN-<slug>.md` paths are accepted too)
|
|
23
|
+
* - `Category`: bug | security | perf | tests | tech-debt | migration |
|
|
24
|
+
* dx | docs | direction
|
|
25
|
+
* - `Planned at`: `commit <short SHA>, <YYYY-MM-DD>` — `commit unknown`
|
|
26
|
+
* is accepted as the documented fallback (`scaffoldAuditPlan` default
|
|
27
|
+
* when the CLI runs outside a git repo)
|
|
28
|
+
*
|
|
29
|
+
* Every `## Status` block in the document is checked; a document without
|
|
30
|
+
* any block gets `audit.status.missing-block`. Violation codes:
|
|
31
|
+
* `audit.status.missing-block`, `audit.status.missing-field`,
|
|
32
|
+
* `audit.status.invalid-priority|effort|risk|depends-on|category|planned-at`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function validateAuditStatusBlocks(planText: string): GateResult;
|
|
35
|
+
/** A redacted credential occurrence: 1-based line + credential type. */
|
|
36
|
+
export type SecretFinding = {
|
|
37
|
+
line: number;
|
|
38
|
+
type: string;
|
|
39
|
+
};
|
|
40
|
+
/** Result of `redactSecrets`: redacted text + the findings summary. */
|
|
41
|
+
export type RedactResult = {
|
|
42
|
+
text: string;
|
|
43
|
+
findings: SecretFinding[];
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Scan text for credential patterns and replace every occurrence with a
|
|
47
|
+
* `[REDACTED <type>@<line> in <file>]` marker (file omitted when `filePath`
|
|
48
|
+
* is not provided), per mstar-audit Hard Rule 4. Newlines are preserved so
|
|
49
|
+
* line numbers stay stable. Returns the redacted text plus a deduplicated,
|
|
50
|
+
* line-sorted findings summary (`{ line, type }`).
|
|
51
|
+
*/
|
|
52
|
+
export declare function redactSecrets(text: string, filePath?: string): RedactResult;
|
|
53
|
+
/** One audit finding, shaped after finding-format.md. */
|
|
54
|
+
export type AuditFinding = {
|
|
55
|
+
title: string;
|
|
56
|
+
category: AuditCategory;
|
|
57
|
+
impact: string;
|
|
58
|
+
effort: AuditEffort;
|
|
59
|
+
risk: AuditRisk;
|
|
60
|
+
confidence: "HIGH" | "MED" | "LOW";
|
|
61
|
+
evidence: readonly string[];
|
|
62
|
+
priority: AuditPriority;
|
|
63
|
+
fixSketch?: string;
|
|
64
|
+
verification?: string;
|
|
65
|
+
dependsOn?: string;
|
|
66
|
+
};
|
|
67
|
+
/** Options for `scaffoldAuditPlan`. `plannedAt` defaults to the
|
|
68
|
+
* `repoShortSha` + `date`; `date` defaults to today (YYYY-MM-DD). */
|
|
69
|
+
export type ScaffoldAuditPlanOptions = {
|
|
70
|
+
date?: string;
|
|
71
|
+
repoName?: string;
|
|
72
|
+
repoShortSha?: string;
|
|
73
|
+
plannedAt?: {
|
|
74
|
+
commit: string;
|
|
75
|
+
date: string;
|
|
76
|
+
};
|
|
77
|
+
rejected?: readonly {
|
|
78
|
+
title: string;
|
|
79
|
+
reason: string;
|
|
80
|
+
}[];
|
|
81
|
+
};
|
|
82
|
+
/** Result of `scaffoldAuditPlan`. `nextNumber` is the next free plan number
|
|
83
|
+
* (monotonic continuation vs. any pre-existing `NNN-*.md` files). */
|
|
84
|
+
export type ScaffoldAuditPlanResult = {
|
|
85
|
+
outDir: string;
|
|
86
|
+
date: string;
|
|
87
|
+
files: string[];
|
|
88
|
+
nextNumber: number;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Scaffold an audit plan directory (`{PLAN_DIR}/audit-<date>/` layout,
|
|
92
|
+
* mstar-audit SKILL § Phase 4): numbered `NNN-<slug>.md` plan files from
|
|
93
|
+
* findings plus a README.md index. Numbering is monotonic — when the
|
|
94
|
+
* directory already contains `NNN-*.md` files (same-date re-run), the new
|
|
95
|
+
* batch continues after the highest existing number instead of restarting
|
|
96
|
+
* at 001, and the rebuilt index includes the pre-existing plans. Rejected
|
|
97
|
+
* findings render in the "considered and rejected" section.
|
|
98
|
+
*/
|
|
99
|
+
export declare function scaffoldAuditPlan(outDir: string, findings: readonly AuditFinding[], options?: ScaffoldAuditPlanOptions): ScaffoldAuditPlanResult;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { GateResult } from "./core.js";
|
|
2
|
+
/** Required frontmatter fields (schema.yaml required_fields). */
|
|
3
|
+
export declare const KNOWLEDGE_REQUIRED_FIELDS: readonly ["module", "date", "problem_type", "category", "severity"];
|
|
4
|
+
/** All problem_type enum values (bug track + knowledge track). */
|
|
5
|
+
export declare const KNOWLEDGE_PROBLEM_TYPES: readonly ["build_error", "test_failure", "runtime_error", "performance_issue", "database_issue", "security_issue", "ui_bug", "integration_issue", "logic_error", "config_error", "developer_experience", "workflow_issue", "best_practice", "documentation_gap", "architecture_pattern", "design_pattern", "tooling_decision", "convention", "api_design", "testing_pattern"];
|
|
6
|
+
/** Bug-track problem_types (schema.yaml tracks.bug). */
|
|
7
|
+
export declare const KNOWLEDGE_BUG_PROBLEM_TYPES: readonly ["build_error", "test_failure", "runtime_error", "performance_issue", "database_issue", "security_issue", "ui_bug", "integration_issue", "logic_error", "config_error"];
|
|
8
|
+
/** Knowledge-track problem_types (schema.yaml tracks.knowledge). */
|
|
9
|
+
export declare const KNOWLEDGE_KNOWLEDGE_PROBLEM_TYPES: readonly ["developer_experience", "workflow_issue", "best_practice", "documentation_gap", "architecture_pattern", "design_pattern", "tooling_decision", "convention", "api_design", "testing_pattern"];
|
|
10
|
+
/** Severity enum (schema.yaml required_fields.severity). */
|
|
11
|
+
export declare const KNOWLEDGE_SEVERITIES: readonly ["critical", "high", "medium", "low"];
|
|
12
|
+
/** Bug-track resolution_type enum (schema.yaml track_rules.bug). */
|
|
13
|
+
export declare const KNOWLEDGE_RESOLUTION_TYPES: readonly ["code_fix", "migration", "config_change", "test_fix", "dependency_update", "environment_setup", "workflow_improvement", "documentation_update", "tooling_addition"];
|
|
14
|
+
/** problem_type → category directory (category-mapping.md; rule 1: the
|
|
15
|
+
* frontmatter `category` must match the mapped directory name). */
|
|
16
|
+
export declare const KNOWLEDGE_CATEGORY_MAP: Readonly<Record<string, string>>;
|
|
17
|
+
/**
|
|
18
|
+
* Validate a knowledge-doc frontmatter against the schema.yaml contract
|
|
19
|
+
* (embedded constants — no runtime skill-file reads):
|
|
20
|
+
* required fields (module, date, problem_type, category, severity), enum
|
|
21
|
+
* values, track rules (bug track needs symptoms / root_cause /
|
|
22
|
+
* resolution_type), category↔problem_type mapping consistency, and optional
|
|
23
|
+
* fields (plan_id, tags ≤ 8, last_updated, related_components).
|
|
24
|
+
*
|
|
25
|
+
* Violation codes:
|
|
26
|
+
* - `compound.schema.missing-frontmatter` — no `---` block
|
|
27
|
+
* - `compound.schema.missing-field` — required field absent/empty
|
|
28
|
+
* - `compound.schema.invalid-date` / `invalid-problem-type` /
|
|
29
|
+
* `invalid-severity` / `invalid-resolution-type`
|
|
30
|
+
* - `compound.schema.category-mismatch` — category ≠ mapping for
|
|
31
|
+
* problem_type (category-mapping.md rule 1)
|
|
32
|
+
* - `compound.schema.missing-track-field` — bug track missing
|
|
33
|
+
* symptoms/root_cause/resolution_type
|
|
34
|
+
* - `compound.schema.invalid-symptoms` / `invalid-root-cause` /
|
|
35
|
+
* `invalid-applies-when` / `invalid-plan-id` / `invalid-tags` /
|
|
36
|
+
* `tags-too-many` / `invalid-last-updated` / `invalid-related-components`
|
|
37
|
+
*/
|
|
38
|
+
export declare function validateSchemaYaml(frontmatterText: string): GateResult;
|
|
39
|
+
/** Result of `referenceExists`: gate verdict + number of refs verified. */
|
|
40
|
+
export type ReferenceCheckResult = GateResult & {
|
|
41
|
+
checked: number;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Check that paths/functions referenced in a knowledge doc exist on disk
|
|
45
|
+
* (compound-refresh Phase 2 item 1: "Referenced code still exists?").
|
|
46
|
+
*
|
|
47
|
+
* Backticked refs are classified conservatively:
|
|
48
|
+
* - path-like refs (contain `/` or end in a known file extension) are
|
|
49
|
+
* resolved against `repoRoot` and must exist — `:line` suffixes and
|
|
50
|
+
* `#anchors` are stripped first; violation `compound.reference.missing-file`.
|
|
51
|
+
* - `module.symbol` refs use a documented heuristic: a module file named
|
|
52
|
+
* `<module>.ts|tsx|js|jsx|mjs|cjs` must exist somewhere under `repoRoot`;
|
|
53
|
+
* violation `compound.reference.module-missing` (low severity — heuristic).
|
|
54
|
+
* - URLs, `{PLACEHOLDER}` refs, globs, absolute paths, and bare symbols are
|
|
55
|
+
* skipped (not repo-relative, or not resolvable deterministically).
|
|
56
|
+
* `checked` counts unique refs that verified.
|
|
57
|
+
*/
|
|
58
|
+
export declare function referenceExists(repoRoot: string, docText: string): ReferenceCheckResult;
|
|
59
|
+
/**
|
|
60
|
+
* Assert every knowledge doc under `knowledgeDir` has a row in
|
|
61
|
+
* `knowledgeDir/README.md` (mstar-compound SKILL.md Phase 6 index
|
|
62
|
+
* obligations). Row first cells may be plain paths or `[title](path)`
|
|
63
|
+
* links, with optional `./` / `knowledge/` prefixes. `README.md` and
|
|
64
|
+
* `index.md` files are not docs.
|
|
65
|
+
*
|
|
66
|
+
* Violation codes:
|
|
67
|
+
* - `compound.index.missing-readme` — no README.md index
|
|
68
|
+
* - `compound.index.missing-row` — doc with no index row
|
|
69
|
+
*/
|
|
70
|
+
export declare function assertIndexRows(knowledgeDir: string): GateResult;
|
|
71
|
+
/**
|
|
72
|
+
* The compound-refresh scope (mstar-compound-refresh SKILL.md §
|
|
73
|
+
* 产物与操作路径): `{HARNESS_DIR}/knowledge/**`, `knowledge/README.md`,
|
|
74
|
+
* `<repo-root>/CONCEPTS.md`, `{HARNESS_DIR}/status.json`.
|
|
75
|
+
*/
|
|
76
|
+
export declare function compoundRefreshScope(harnessDir: string, projectRoot: string): string[];
|
|
77
|
+
/**
|
|
78
|
+
* Guard an operation path against the allowed root set (compound-refresh
|
|
79
|
+
* scope SSOT: only knowledge/**, knowledge/README.md, CONCEPTS.md, and
|
|
80
|
+
* status.json may be written). File-like roots require an exact match;
|
|
81
|
+
* directory roots allow any path beneath them. `..` traversal-out is
|
|
82
|
+
* rejected via `resolve()` normalization.
|
|
83
|
+
*
|
|
84
|
+
* Limitation (documented): the guard is lexical — `resolve()` never follows
|
|
85
|
+
* symlinks, so a symlink inside an allowed root that points outside is not
|
|
86
|
+
* detected. Real enforcement is host-side (the sandbox/approval layer).
|
|
87
|
+
*
|
|
88
|
+
* Violation code: `compound.scope.outside` (medium).
|
|
89
|
+
*/
|
|
90
|
+
export declare function scopeGuard(path: string, allowedRoots: readonly string[]): GateResult;
|
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Severity levels used across harness validation results.
|
|
3
|
+
*
|
|
4
|
+
* Machine SSOT — `mstar-plan-artifacts/references/status-and-residuals.md`
|
|
5
|
+
* § "Residual findings: `severity` (SSOT, machine field)" defines the same
|
|
6
|
+
* five lowercase-English values; `warning` / `Major` / any other value are
|
|
7
|
+
* forbidden in JSON severity fields.
|
|
8
|
+
*/
|
|
9
|
+
export type Severity = "critical" | "high" | "medium" | "low" | "nit";
|
|
10
|
+
/**
|
|
11
|
+
* Total order, heavy → light (spec: status-and-residuals.md § severity):
|
|
12
|
+
* `critical` > `high` > `medium` > `low` > `nit`. `nit` is always lighter
|
|
13
|
+
* than `low` — never invert or equate.
|
|
14
|
+
*/
|
|
15
|
+
export declare const SEVERITY_ORDER: readonly Severity[];
|
|
16
|
+
/**
|
|
17
|
+
* Result of one harness validation check.
|
|
18
|
+
*
|
|
19
|
+
* Shape per `.harness/references/skill-programmatic-roadmap.md` §8.5 C4:
|
|
20
|
+
* `{ ok: boolean, severity, code, message, fix? }`. v1 enforcement is
|
|
21
|
+
* non-blocking: callers (CLI output readers, host hooks) surface it as a
|
|
22
|
+
* warning, never a hard stop.
|
|
23
|
+
*/
|
|
24
|
+
export type ValidationResult = {
|
|
25
|
+
ok: boolean;
|
|
26
|
+
severity: Severity;
|
|
27
|
+
code: string;
|
|
28
|
+
message: string;
|
|
29
|
+
fix?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Backward-compat alias codes for the same violation, emitted by the
|
|
32
|
+
* engine's single parser (e.g. the Slice-2 `assignment.presence.*`
|
|
33
|
+
* namespace kept as aliases on the three core Assignment field
|
|
34
|
+
* violations — see `dispatch.requireField`). Consumers keyed off either
|
|
35
|
+
* namespace see exactly ONE violation per missing field.
|
|
36
|
+
*/
|
|
37
|
+
aliases?: readonly string[];
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Aggregate of validation checks for one gate (spec: roadmap §8.2 core row).
|
|
41
|
+
* `ok` is the gate verdict over `violations` — a gate with any violation
|
|
42
|
+
* does not pass.
|
|
43
|
+
*
|
|
44
|
+
* `hardBlocked` (Slice 5 / roadmap §8.5 C4 + D2) is the hard-enforcement
|
|
45
|
+
* overlay: `true` when the gate has violations AND the caller requested hard
|
|
46
|
+
* mode via `applyEnforcement`. Absent/`false` means warn-only — the caller
|
|
47
|
+
* may proceed with a warning. A caller that can refuse an action MUST refuse
|
|
48
|
+
* when `hardBlocked === true`.
|
|
49
|
+
*/
|
|
50
|
+
export type GateResult = {
|
|
51
|
+
ok: boolean;
|
|
52
|
+
violations: ValidationResult[];
|
|
53
|
+
hardBlocked?: boolean;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Apply hard-enforcement semantics to a gate result (roadmap §8.5 C4/D2):
|
|
57
|
+
* `hardBlocked` is `true` exactly when `hard` is requested AND the gate has
|
|
58
|
+
* violations. `ok` and `violations` are preserved — enforcement is an
|
|
59
|
+
* overlay on the verdict, never a re-validation. When `hard` is false
|
|
60
|
+
* (flag absent/unset) the result is warn-only (`hardBlocked: false`), so
|
|
61
|
+
* rollback is simply unsetting the flag. Returns a NEW result; the input
|
|
62
|
+
* gate is not mutated.
|
|
63
|
+
*/
|
|
64
|
+
export declare function applyEnforcement(gate: GateResult, opts: {
|
|
65
|
+
hard: boolean;
|
|
66
|
+
}): GateResult;
|
|
67
|
+
/**
|
|
68
|
+
* Read and parse a JSON document. Missing or empty files read as `{}`
|
|
69
|
+
* (same contract as the CLI helper this consolidates); malformed JSON
|
|
70
|
+
* throws with the file path in the message.
|
|
71
|
+
*/
|
|
72
|
+
export declare function readJson(filePath: string): Record<string, unknown>;
|
|
73
|
+
/**
|
|
74
|
+
* Serialize `value` as pretty JSON with a trailing newline and write it
|
|
75
|
+
* atomically: temp file in the same directory, then rename over the target.
|
|
76
|
+
* Creates parent directories as needed; on failure the temp file is removed
|
|
77
|
+
* and the error rethrown, so the target is never partially written.
|
|
78
|
+
*
|
|
79
|
+
* Durability note (qc2 F-013): no fsync before the rename — atomicity (no
|
|
80
|
+
* partial file) is guaranteed by the same-dir temp + rename, but a power
|
|
81
|
+
* loss immediately after rename may lose the write. Acceptable for
|
|
82
|
+
* coordination files (status.json) whose writers re-read + verify the
|
|
83
|
+
* stored state; revisit if the harness moves to a filesystem without
|
|
84
|
+
* rename-atomicity guarantees.
|
|
85
|
+
*/
|
|
86
|
+
export declare function writeJson(filePath: string, value: Record<string, unknown>): void;
|
|
87
|
+
/**
|
|
88
|
+
* Resolve the project root by walking up from `startDir` (default: cwd) to
|
|
89
|
+
* the nearest ancestor containing `package.json` or `bun.lock`. Falls back
|
|
90
|
+
* to the resolved `startDir` when no marker exists up to the filesystem root.
|
|
91
|
+
*/
|
|
92
|
+
export declare function resolveProjectRoot(startDir?: string): string;
|
|
93
|
+
/**
|
|
94
|
+
* Resolve the harness version from a module directory: the module's OWN
|
|
95
|
+
* manifest first (`<moduleDir>/../package.json` — always shipped by npm,
|
|
96
|
+
* e.g. `node_modules/@mstar-harness/engine/package.json` next to
|
|
97
|
+
* `dist/engine.js`, or the CLI/opencode package.json next to their bundles),
|
|
98
|
+
* falling back to the monorepo root `morning-star` `package.json` walk.
|
|
99
|
+
* The single-version invariant makes both equivalent in-repo; the
|
|
100
|
+
* own-manifest-first order fixes published installs, where no
|
|
101
|
+
* `morning-star` manifest exists anywhere above `node_modules` and the walk
|
|
102
|
+
* alone would regress to `"0.0.0"` (qc3 F-1).
|
|
103
|
+
*/
|
|
104
|
+
export declare function harnessVersionFrom(moduleDir: string): string;
|
|
105
|
+
/**
|
|
106
|
+
* Read the harness version (own-manifest first — see `harnessVersionFrom`).
|
|
107
|
+
*
|
|
108
|
+
* Single source for the harness version inside TS (roadmap §8.5 C6, moved
|
|
109
|
+
* from `packages/cli/src/utils.ts`); the CLI re-exports it unchanged. The
|
|
110
|
+
* single-version invariant keeps root, engine, cli and opencode aligned.
|
|
111
|
+
*/
|
|
112
|
+
export declare function readHarnessVersion(): string;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engine design-md module — DESIGN.md token frontmatter validation, light/dark
|
|
3
|
+
* parity, and completeness level audit.
|
|
4
|
+
*
|
|
5
|
+
* Spec sources (all embedded as constants — no runtime skill-file reads):
|
|
6
|
+
* - mstar-design-md SKILL.md: YAML frontmatter is the SSOT for token values;
|
|
7
|
+
* dual theme = same token names, different values; three completeness
|
|
8
|
+
* levels with LEVEL2/LEVEL3_PLACEHOLDER upgrade markers.
|
|
9
|
+
* - mstar-design-md/references/design-md-spec.md §1.5 (token store: colors /
|
|
10
|
+
* typography / spacing / rounded / components shapes), §3 (token naming),
|
|
11
|
+
* §4 (light/dark contract), §5 (upgrade placeholders), §6 ({path} refs).
|
|
12
|
+
* - mstar-design-md/references/completeness-checklist.md: Level 1–3 items;
|
|
13
|
+
* placeholders (`"[...]"`) never count as complete.
|
|
14
|
+
*
|
|
15
|
+
* Judgment stays prompt — this module is the deterministic half.
|
|
16
|
+
*/
|
|
17
|
+
import type { GateResult } from "./core.js";
|
|
18
|
+
/** Parsed DESIGN.md frontmatter. Group values are `unknown` because a
|
|
19
|
+
* malformed group (e.g. `colors: red`) is a scalar; validation reports it. */
|
|
20
|
+
export type DesignFrontmatter = {
|
|
21
|
+
version?: string;
|
|
22
|
+
name?: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
colors: Record<string, unknown>;
|
|
25
|
+
typography: Record<string, unknown>;
|
|
26
|
+
spacing: Record<string, unknown>;
|
|
27
|
+
rounded: Record<string, unknown>;
|
|
28
|
+
components: Record<string, unknown>;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Parse the leading `---`-fenced YAML frontmatter of a DESIGN.md into the
|
|
32
|
+
* five token groups plus version/name/description. Commented-out lines
|
|
33
|
+
* (including `# LEVEL2_PLACEHOLDER:` markers) are skipped, so only ACTIVE
|
|
34
|
+
* tokens count — matching the checklist rule that commented keys are not
|
|
35
|
+
* complete. Returns `null` when the text has no frontmatter block.
|
|
36
|
+
*
|
|
37
|
+
* A token group whose YAML value is a scalar is stored as
|
|
38
|
+
* `{ [RAW_GROUP]: <value> }` so validation can report `group-not-map`.
|
|
39
|
+
*/
|
|
40
|
+
export declare function parseDesignFrontmatter(frontmatterText: string): DesignFrontmatter | null;
|
|
41
|
+
/**
|
|
42
|
+
* Validate a DESIGN.md frontmatter against the token-store contract
|
|
43
|
+
* (design-md-spec §1.5): the colors/typography/spacing/rounded groups must
|
|
44
|
+
* exist with spec-typed values, `components` (optional below Level 2) must
|
|
45
|
+
* carry `{path}` references that resolve within the same file.
|
|
46
|
+
*
|
|
47
|
+
* Violation codes:
|
|
48
|
+
* - `design-md.tokens.missing-frontmatter` — no `---` block
|
|
49
|
+
* - `design-md.tokens.missing-group` — required group absent/empty
|
|
50
|
+
* - `design-md.tokens.group-not-map` — group value is a scalar
|
|
51
|
+
* - `design-md.tokens.color-format` — not hex or oklch()
|
|
52
|
+
* - `design-md.tokens.typography-shape` — not exactly the five properties
|
|
53
|
+
* - `design-md.tokens.spacing-base` / `spacing-key` / `spacing-format`
|
|
54
|
+
* - `design-md.tokens.rounded-format` — not a px length
|
|
55
|
+
* - `design-md.tokens.components-shape` — component entry not a map
|
|
56
|
+
* - `design-md.tokens.ref-unresolved` — `{group.key}` does not resolve
|
|
57
|
+
* - `design-md.tokens.placeholder` — `"[...]"` template value (low)
|
|
58
|
+
*/
|
|
59
|
+
export declare function validateDesignTokenFrontmatter(frontmatterText: string): GateResult;
|
|
60
|
+
/**
|
|
61
|
+
* Assert the light/dark dual-theme contract (design-md-spec §4 rules 1–4):
|
|
62
|
+
* both files define the SAME token key set across all five groups; only
|
|
63
|
+
* values differ. Every token active in one file must be active in the other.
|
|
64
|
+
*
|
|
65
|
+
* Violation codes:
|
|
66
|
+
* - `design-md.parity.missing-frontmatter` — either file has no block
|
|
67
|
+
* - `design-md.parity.missing-dark` — token present in light only
|
|
68
|
+
* - `design-md.parity.missing-light` — token present in dark only
|
|
69
|
+
*/
|
|
70
|
+
export declare function assertLightDarkParity(lightFm: string, darkFm: string): GateResult;
|
|
71
|
+
/** Completeness level verdict (checklist § Verdict). */
|
|
72
|
+
export type CompletenessLevel = "BELOW_MVP" | "MVP" | "Standard" | "Production";
|
|
73
|
+
/** One checklist item: id, owning level, source, and pass/fail. */
|
|
74
|
+
export type CompletenessItem = {
|
|
75
|
+
id: string;
|
|
76
|
+
level: 1 | 2 | 3;
|
|
77
|
+
ok: boolean;
|
|
78
|
+
source: "frontmatter" | "body";
|
|
79
|
+
};
|
|
80
|
+
/** A `LEVEL2_PLACEHOLDER` / `LEVEL3_PLACEHOLDER` marker found in the text. */
|
|
81
|
+
export type CompletenessPlaceholder = {
|
|
82
|
+
level: 2 | 3;
|
|
83
|
+
marker: string;
|
|
84
|
+
line: number;
|
|
85
|
+
};
|
|
86
|
+
/** Result of `completenessLevel`. `bodyUnverified` is true when the caller
|
|
87
|
+
* passed no checklist (body-only items were excluded from the level
|
|
88
|
+
* computation and Production is capped at Standard). */
|
|
89
|
+
export type CompletenessResult = {
|
|
90
|
+
level: CompletenessLevel;
|
|
91
|
+
items: CompletenessItem[];
|
|
92
|
+
/** Ids failing at the next-highest level boundary (or the current level
|
|
93
|
+
* when below MVP). */
|
|
94
|
+
missing: string[];
|
|
95
|
+
placeholders: CompletenessPlaceholder[];
|
|
96
|
+
upgradeTo: 2 | 3 | null;
|
|
97
|
+
bodyUnverified: boolean;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Determine the DESIGN.md completeness level from its frontmatter
|
|
101
|
+
* (completeness-checklist.md): Level 1 (MVP) → Level 2 (Standard) → Level 3
|
|
102
|
+
* (Production). A level is complete only when all items at that level and
|
|
103
|
+
* below pass; placeholder values and commented-out keys never count.
|
|
104
|
+
*
|
|
105
|
+
* Body-only items (breakpoints, elevation, motion, voice, dark-file) cannot
|
|
106
|
+
* be verified from frontmatter — pass their ids in `checklist` to confirm
|
|
107
|
+
* them. When `checklist` is omitted, body items are excluded from level
|
|
108
|
+
* computation and Production is capped at Standard (`bodyUnverified: true`).
|
|
109
|
+
*
|
|
110
|
+
* Placeholder detection scans for `LEVEL2_PLACEHOLDER` / `LEVEL3_PLACEHOLDER`
|
|
111
|
+
* markers (design-md-spec §5) and suggests the implied upgrade target.
|
|
112
|
+
*/
|
|
113
|
+
export declare function completenessLevel(frontmatterText: string, checklist?: readonly string[]): CompletenessResult;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engine dispatch module — Assignment field validation, default-branch gate,
|
|
3
|
+
* execution-mode → QC seat count, tri identity, anti-recursion precheck.
|
|
4
|
+
*
|
|
5
|
+
* Spec sources (semantic SSOT — the skills stay authoritative; this module
|
|
6
|
+
* implements their deterministic rules without forking semantics):
|
|
7
|
+
* - Assignment field contract (`Execute as` / `Delegation` / `Task category`
|
|
8
|
+
* present, non-empty; paste-only assignments missing fields are flagged):
|
|
9
|
+
* `mstar-dispatch-gates` SKILL.md § "调度防串扰(强制)" + § 反模式(派发)
|
|
10
|
+
* ("Assignment 已写、invoke 为零(paste-only)").
|
|
11
|
+
* - Branch-field exactly-one rule + `<base>` requirement: `mstar-branch-worktree`
|
|
12
|
+
* SKILL.md § "Assignment 要求(PM)" + § "`<base>` 与叠分支(stacked
|
|
13
|
+
* branches)" ("若写新建但未写 `<base>`:实现侧应停下问 project-manager…
|
|
14
|
+
* 禁止擅自假设「一定是 main」").
|
|
15
|
+
* - Default-protected-branch gate (`main`/`master` unless an explicit
|
|
16
|
+
* `Branch policy: direct on <branch> — <reason>` exception exists):
|
|
17
|
+
* `mstar-branch-worktree` SKILL.md § "Git 功能分支门禁(业务仓库)".
|
|
18
|
+
* - N→seat mapping (sdd→3 tri, inline→1 single, targeted→listed seats) and
|
|
19
|
+
* tri identity (`qc-specialist` / `qc-specialist-2` / `qc-specialist-3`):
|
|
20
|
+
* `mstar-dispatch-gates` SKILL.md § "QC tri-review(SDD 强制)" / "QC
|
|
21
|
+
* 单席(例外)" / "QC targeted re-review" + `mstar-roles` SKILL.md
|
|
22
|
+
* "QC reviewer" 参数表.
|
|
23
|
+
* - Anti-recursion NEVER red line (role binding == `Execute as`): `mstar-dispatch-gates`
|
|
24
|
+
* SKILL.md § "承接方反递归红线(NEVER / DO NOT;leaf executor 必读)".
|
|
25
|
+
* - Hard-gate enforcement (`Enforcement: hard` flag — per Assignment/compass,
|
|
26
|
+
* never global; rollback = unset flag): `.harness/references/skill-programmatic-roadmap.md`
|
|
27
|
+
* §8.5 C4 + decision D2.
|
|
28
|
+
*/
|
|
29
|
+
import type { GateResult } from "./core.js";
|
|
30
|
+
/** Parsed Assignment header fields relevant to dispatch validation. */
|
|
31
|
+
export type AssignmentFields = {
|
|
32
|
+
executeAs?: string;
|
|
33
|
+
delegation?: string;
|
|
34
|
+
taskCategory?: string;
|
|
35
|
+
workingBranch?: string;
|
|
36
|
+
branchPolicy?: string;
|
|
37
|
+
};
|
|
38
|
+
export type ValidateAssignmentFieldsOptions = {
|
|
39
|
+
/**
|
|
40
|
+
* Whether the assignment produces repo diffs (default `true`). The
|
|
41
|
+
* branch-form exactly-one gate applies to writable assignments only —
|
|
42
|
+
* read-only assignments (explore/scout orientation) legitimately omit
|
|
43
|
+
* branch fields per mstar-branch-worktree ("每个可写 Assignment…").
|
|
44
|
+
*/
|
|
45
|
+
writable?: boolean;
|
|
46
|
+
};
|
|
47
|
+
/** Options for {@link assertDefaultBranchProtected}. */
|
|
48
|
+
export type DefaultBranchOptions = {
|
|
49
|
+
/** Protected default branch names (project convention; default `main`/`master`). */
|
|
50
|
+
defaultBranches?: readonly string[];
|
|
51
|
+
/** True when the Assignment carries an explicit `Branch policy: direct on …` exception. */
|
|
52
|
+
directOnException?: boolean;
|
|
53
|
+
};
|
|
54
|
+
/** Options for {@link executionModeToN}. */
|
|
55
|
+
export type ExecutionModeToNOptions = {
|
|
56
|
+
/** Listed reviewer seats for `targeted` re-review (`QC re-review: targeted — reviewers: …`). */
|
|
57
|
+
seats?: readonly string[];
|
|
58
|
+
};
|
|
59
|
+
/** Result of {@link executionModeToN}: a GateResult carrying `n` on success. */
|
|
60
|
+
export type ExecutionModeToNResult = GateResult & {
|
|
61
|
+
n?: number;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Parse `**Field**: value` (or plain `Field: value`) header lines from an
|
|
65
|
+
* Assignment into the dispatch-relevant fields. Only known field labels are
|
|
66
|
+
* captured; values are trimmed. List-bullet prefixes (`- **Field**: value`)
|
|
67
|
+
* are accepted so the engine parser is the SINGLE grammar for Assignment
|
|
68
|
+
* header fields (the Slice-2 opencode presence parser tolerated bullets;
|
|
69
|
+
* its acceptance is folded into this parser, not forked — qc1 F-002).
|
|
70
|
+
*/
|
|
71
|
+
export declare function parseAssignmentFields(assignmentText: string): AssignmentFields;
|
|
72
|
+
/** Where the `Enforcement` flag was declared (roadmap §8.5 C4/D2). */
|
|
73
|
+
export type EnforcementSource = "assignment" | "compass" | "none";
|
|
74
|
+
/** Parsed hard-enforcement flag. `hard: false` + `source: none` = flag absent. */
|
|
75
|
+
export type EnforcementFlag = {
|
|
76
|
+
hard: boolean;
|
|
77
|
+
source: EnforcementSource;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Slice an Assignment's header region — the text before the first body
|
|
81
|
+
* marker (see {@link ASSIGNMENT_BODY_START_RE}). Returns the full text when
|
|
82
|
+
* no marker is present. The Assignment enforcement flag is parsed against
|
|
83
|
+
* THIS region only, so an example line `**Enforcement**: hard` quoted in the
|
|
84
|
+
* task body cannot harden the dispatch (qc1 F-003 / qc2 F-003).
|
|
85
|
+
*/
|
|
86
|
+
export declare function assignmentHeaderRegion(assignmentText: string): string;
|
|
87
|
+
/**
|
|
88
|
+
* Parse the `Enforcement: hard` flag (roadmap §8.5 C4 + decision D2 — v2
|
|
89
|
+
* hard gates are OPT-IN per Assignment/compass, never global; rollback =
|
|
90
|
+
* unset flag; inert when the engine is absent).
|
|
91
|
+
*
|
|
92
|
+
* Recognized forms, checked in order:
|
|
93
|
+
* 1. Assignment header `**Enforcement**: hard` / `Enforcement: hard`
|
|
94
|
+
* (bold or plain, optional list bullet; value case-insensitive).
|
|
95
|
+
* 2. Compass frontmatter YAML key `enforcement: hard` (lowercase key;
|
|
96
|
+
* value may be quoted, case-insensitive).
|
|
97
|
+
*
|
|
98
|
+
* The Assignment form wins over the compass form when both appear in the
|
|
99
|
+
* input (per-Assignment precedence — a dispatch's own flag is decisive).
|
|
100
|
+
* A present-but-non-hard value (`soft`, empty, malformed) still reports
|
|
101
|
+
* its source so callers can distinguish "explicitly not hard" from
|
|
102
|
+
* "not mentioned" (`source: none`). Never throws.
|
|
103
|
+
*
|
|
104
|
+
* Assignment-form callers MUST pass the header region (see
|
|
105
|
+
* {@link assignmentHeaderRegion}) — this function itself scans the whole
|
|
106
|
+
* input because the compass form is fed raw frontmatter, which has no
|
|
107
|
+
* body markers (qc1 F-003 / qc2 F-003).
|
|
108
|
+
*/
|
|
109
|
+
export declare function parseEnforcementFlag(text: string): EnforcementFlag;
|
|
110
|
+
/**
|
|
111
|
+
* Parsed branch forms of an Assignment (mstar-branch-worktree § "Assignment
|
|
112
|
+
* 要求"): `Working branch: <existing>` | `Working branch: create <new> from
|
|
113
|
+
* <base>` | `Branch policy: direct on <branch> — <reason>`. Exactly one is
|
|
114
|
+
* required for writable assignments. This is the engine's SINGLE branch-form
|
|
115
|
+
* grammar — CLI and host hooks consume it instead of re-implementing the
|
|
116
|
+
* regexes (qc1 F-001 / qc3 F-3).
|
|
117
|
+
*/
|
|
118
|
+
export type AssignmentBranchForms = {
|
|
119
|
+
/**
|
|
120
|
+
* `Working branch: <existing>` — the value's first token (create-form
|
|
121
|
+
* values are excluded and land in {@link createForm} instead).
|
|
122
|
+
*/
|
|
123
|
+
workingBranch?: string;
|
|
124
|
+
/** `Working branch: create <new> from <base>` — created branch name (+ base when written). */
|
|
125
|
+
createForm?: {
|
|
126
|
+
name: string;
|
|
127
|
+
base?: string;
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* `Branch policy: direct on <branch> — <reason>` — branch captured by the
|
|
131
|
+
* loose `direct on <branch>` prefix; `reason` is the strict-form reason
|
|
132
|
+
* ("" when the value is not a well-formed direct-on form, i.e. no
|
|
133
|
+
* separator + non-empty reason — mirror of `validateAssignmentFields`).
|
|
134
|
+
*/
|
|
135
|
+
directOn?: {
|
|
136
|
+
branch: string;
|
|
137
|
+
reason: string;
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* Parse an Assignment's branch forms via the engine's single parser
|
|
142
|
+
* (`parseAssignmentFields` + {@link parseWorkingBranchValue}). Consumed by
|
|
143
|
+
* the CLI `dispatch validate` gate-branch derivation and the opencode hook;
|
|
144
|
+
* also the internal grammar behind `validateAssignmentFields`.
|
|
145
|
+
*/
|
|
146
|
+
export declare function parseAssignmentBranchForms(assignmentText: string): AssignmentBranchForms;
|
|
147
|
+
/**
|
|
148
|
+
* Parse the Assignment's `Branch policy: direct on <branch> — <reason>`
|
|
149
|
+
* exception branch. Returns the branch ONLY for the well-formed direct-on
|
|
150
|
+
* form (branch + non-empty reason; separator set [—–]|--|-); undefined when
|
|
151
|
+
* absent or malformed — the default-branch gate recognizes explicit
|
|
152
|
+
* direct-on exceptions only. Single engine grammar shared by CLI + plugin
|
|
153
|
+
* (qc1 F-001).
|
|
154
|
+
*/
|
|
155
|
+
export declare function parseBranchPolicyDirectOnBranch(assignmentText: string): string | undefined;
|
|
156
|
+
/**
|
|
157
|
+
* True when the Assignment's `Execute as` role is a read-only orientation
|
|
158
|
+
* role (`scout` / `explore`, case-insensitive). Read-only assignments
|
|
159
|
+
* legitimately omit branch forms (mstar-branch-worktree § "每个可写
|
|
160
|
+
* Assignment…") — callers pass `validateAssignmentFields(text, { writable:
|
|
161
|
+
* false })` and skip the default-branch gate for them (qc3 F-1 / qc2 S-5).
|
|
162
|
+
*/
|
|
163
|
+
export declare function isReadOnlyAssignmentRole(roleId: string): boolean;
|
|
164
|
+
/**
|
|
165
|
+
* Validate an Assignment's header fields (mstar-dispatch-gates Assignment
|
|
166
|
+
* field contract + mstar-branch-worktree branch-form contract).
|
|
167
|
+
*
|
|
168
|
+
* Required: `Execute as` / `Delegation` / `Task category` present with
|
|
169
|
+
* non-empty values (paste-only shells are caught here — every field missing).
|
|
170
|
+
* Writable assignments must carry EXACTLY ONE branch form; `create <new>
|
|
171
|
+
* from <base>` without `<base>` (incl. the dangling `create <new> from`
|
|
172
|
+
* / `create from <base>` typos) and `Branch policy` without branch/reason
|
|
173
|
+
* are flagged. The three core-field violations carry the legacy
|
|
174
|
+
* `assignment.presence.*` codes as aliases (qc1 F-002).
|
|
175
|
+
*/
|
|
176
|
+
export declare function validateAssignmentFields(assignmentText: string, opts?: ValidateAssignmentFieldsOptions): GateResult;
|
|
177
|
+
/**
|
|
178
|
+
* Flag writable work on a default protected branch (`main`/`master` per
|
|
179
|
+
* project convention) unless an explicit direct-on exception is present
|
|
180
|
+
* (mstar-branch-worktree § "Git 功能分支门禁"). `directOnException` mirrors
|
|
181
|
+
* the Assignment carrying `Branch policy: direct on <branch> — <reason>`.
|
|
182
|
+
*/
|
|
183
|
+
export declare function assertDefaultBranchProtected(branch: string, opts?: DefaultBranchOptions): GateResult;
|
|
184
|
+
/**
|
|
185
|
+
* Map an Assignment `Execution mode` to its QC seat count N
|
|
186
|
+
* (mstar-dispatch-gates § QC tri / 单席 / targeted): `sdd` → 3 (tri),
|
|
187
|
+
* `inline` → 1, `targeted` → the listed reviewer seats. Unknown or missing
|
|
188
|
+
* modes are violations.
|
|
189
|
+
*/
|
|
190
|
+
export declare function executionModeToN(executionMode: string, opts?: ExecutionModeToNOptions): ExecutionModeToNResult;
|
|
191
|
+
/**
|
|
192
|
+
* Assert the initial QC wave's reviewer roles are exactly
|
|
193
|
+
* `qc-specialist` / `qc-specialist-2` / `qc-specialist-3`
|
|
194
|
+
* (mstar-dispatch-gates § QC tri-review; mstar-roles QC reviewer 参数表).
|
|
195
|
+
* Any other composition — missing seat, duplicate, or foreign role — fails.
|
|
196
|
+
*/
|
|
197
|
+
export declare function assertTriIdentity(reviewerRoles: readonly string[]): GateResult;
|
|
198
|
+
/**
|
|
199
|
+
* Anti-recursion precheck (NEVER red line, mstar-dispatch-gates § 承接方反递归
|
|
200
|
+
* 红线): a leaf executor MUST NOT invoke a Task/subagent whose role-binding
|
|
201
|
+
* field (`subagent_type` / `agent` / `subagent`) equals its own `Execute as`.
|
|
202
|
+
* Comparison is case-insensitive after trim; an empty binding is not a
|
|
203
|
+
* self-recursion (its presence is the field gate's job, not this precheck).
|
|
204
|
+
*/
|
|
205
|
+
export declare function antiRecursionPrecheck(subagentType: string, executeAs: string): GateResult;
|