musubix3 0.1.1 → 0.1.2
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/.github/plugin/marketplace.json +2 -2
- package/.github/skills/sdd-implementation/SKILL.md +6 -2
- package/.github/skills/sdd-quality/SKILL.md +13 -10
- package/CHANGELOG.md +15 -0
- package/README-ja.md +22 -7
- package/README.md +23 -7
- package/dist/packages/analysis/src/config.d.ts +10 -0
- package/dist/packages/analysis/src/config.js +100 -8
- package/dist/packages/analysis/src/config.js.map +1 -1
- package/dist/packages/analysis/src/files.js +14 -0
- package/dist/packages/analysis/src/files.js.map +1 -1
- package/dist/packages/analysis/src/mutation.d.ts +15 -1
- package/dist/packages/analysis/src/mutation.js +86 -0
- package/dist/packages/analysis/src/mutation.js.map +1 -1
- package/dist/packages/analysis/src/tdd.js +12 -0
- package/dist/packages/analysis/src/tdd.js.map +1 -1
- package/dist/packages/analysis/src/workflow.js +5 -3
- package/dist/packages/analysis/src/workflow.js.map +1 -1
- package/dist/packages/cli/src/main.js +12 -3
- package/dist/packages/cli/src/main.js.map +1 -1
- package/package.json +1 -1
- package/plugin.json +1 -1
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
"owner": { "name": "nahisaho" },
|
|
4
4
|
"metadata": {
|
|
5
5
|
"description": "GitHub Copilot CLI specification-driven development skills",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.2"
|
|
7
7
|
},
|
|
8
8
|
"plugins": [
|
|
9
9
|
{
|
|
10
10
|
"name": "musubix3",
|
|
11
11
|
"source": ".",
|
|
12
|
-
"version": "0.1.
|
|
12
|
+
"version": "0.1.2",
|
|
13
13
|
"description": "Evidence-driven SDD without duplicating native Copilot capabilities."
|
|
14
14
|
}
|
|
15
15
|
]
|
|
@@ -46,11 +46,15 @@ After the work, run `npx musubix3 workflow-record sdd-implementation complete
|
|
|
46
46
|
Put annotations in the authoritative source and test files, including
|
|
47
47
|
supported non-JS/TS files; never add a proxy file just to increase coverage.
|
|
48
48
|
An annotation establishes a link, not proof that code or tests are correct.
|
|
49
|
-
5.
|
|
49
|
+
5. Before recording Red, run every configured `tdd.redPreflightCommands`
|
|
50
|
+
formatter/check and let musubix3 enforce that preflight. Then run the
|
|
51
|
+
project's actual focused tests/typecheck/build; fix failures.
|
|
50
52
|
When mutation evidence is required, use the project's existing lightweight
|
|
51
53
|
mutation mechanism to emit schema-v1 deterministic identities, requirement/
|
|
52
54
|
test linkage, source/test SHA-256, operator/location, and killed status.
|
|
53
|
-
Never fabricate results or add a large mutation dependency.
|
|
55
|
+
Never fabricate results or add a large mutation dependency. Use
|
|
56
|
+
`npx musubix3 mutation doctor --json` to inspect locally available engines
|
|
57
|
+
and configuration recommendations.
|
|
54
58
|
6. Regenerate `npx musubix3 trace build`, check `trace check --strict`, then
|
|
55
59
|
`npx musubix3 gate --changed`. Configure real command/argument arrays first.
|
|
56
60
|
7. Use Copilot's native review and security-review capabilities when appropriate.
|
|
@@ -8,7 +8,6 @@ Follow the user's input language (日本語 / English). Use native Copilot revie
|
|
|
8
8
|
security review for their specialist reasoning; this skill does not replace them.
|
|
9
9
|
After the work, run `npx musubix3 workflow-record sdd-quality complete --status
|
|
10
10
|
completed` exactly once.
|
|
11
|
-
|
|
12
11
|
1. Inspect `.musubix/config.json` and `.musubix/policy-baseline.json` before
|
|
13
12
|
running commands. Only execute trusted project configuration. Commands are
|
|
14
13
|
executable/argument arrays, not shell text. Never edit the baseline without
|
|
@@ -18,17 +17,19 @@ completed` exactly once.
|
|
|
18
17
|
custom report or a built-in Vitest/Jest, pytest, Go test, Cargo or JUnit
|
|
19
18
|
adapter. Require executable native adapter contracts in CI; JUnit targets use
|
|
20
19
|
an exact `@Tag("TEST-*")`, and pytest requires `pytest-json-report`. Review
|
|
21
|
-
`requiredChecks`, coverage thresholds and architecture rules.
|
|
20
|
+
`requiredChecks`, `qualityProfile`, coverage thresholds and architecture rules.
|
|
21
|
+
Use `recommended` or `release` only when all profile requirements are
|
|
22
|
+
intentionally configured; profile validation never invents missing evidence. Use strict
|
|
22
23
|
`codeGraph.mode` when unresolved computed module loading must block release,
|
|
23
24
|
and ensure the trusted baseline prevents downgrading it. Configure a
|
|
24
25
|
fresh structured `testReport` for `test-identities`. Do not weaken policy.
|
|
25
26
|
3. Run `npx musubix3 gate --json` or `npx musubix3 gate --changed --json`.
|
|
26
|
-
`
|
|
27
|
-
form of the same fail-closed gate pipeline. If `input-stability` fails,
|
|
27
|
+
`evidence refresh --json` runs the same fail-closed pipeline. If `input-stability` fails,
|
|
28
28
|
inspect its per-path added/modified/deleted diagnostics and stop generators
|
|
29
29
|
or formatters before rerunning. Standard dependency/build directories,
|
|
30
|
-
including Cargo and Maven `target
|
|
31
|
-
|
|
30
|
+
including manifest-scoped Cargo and Maven `target/` and conventional `.venv`
|
|
31
|
+
or `venv` roots with a regular `pyvenv.cfg`, are excluded; arbitrary source
|
|
32
|
+
directories and source-like generated inputs are not silently ignored.
|
|
32
33
|
Changed mode reports Git changes and dependent files but conservatively runs
|
|
33
34
|
all checks, including commands. It never treats unrun checks as successful.
|
|
34
35
|
When `tdd` is required, confirm every command has test-scoped `tddArgs`, every
|
|
@@ -40,8 +41,9 @@ completed` exactly once.
|
|
|
40
41
|
Treat workflow records as declarations until `workflow-verify` binds each
|
|
41
42
|
completed declaration to one distinct completed Copilot Skill tool call.
|
|
42
43
|
In strict mode require one final successful result, matching session UUID,
|
|
43
|
-
causal
|
|
44
|
-
transcript hash. Ensure the
|
|
44
|
+
causal transcript order, policy-bounded concurrent-event clock skew, bounded
|
|
45
|
+
freshness, complete tool lifecycles and canonical transcript hash. Ensure the
|
|
46
|
+
baseline protects strict/session/freshness/event-skew policy.
|
|
45
47
|
When `change-history` is required,
|
|
46
48
|
confirm ordered checkpoints, chained TDD records, requirement-scoped Code
|
|
47
49
|
Graph changes, exact CHANGE requirement enumeration, measurable Acceptance,
|
|
@@ -59,7 +61,8 @@ completed` exactly once.
|
|
|
59
61
|
identities bound to must functional requirements, current source/test
|
|
60
62
|
fingerprints, operator/location, authoritative tests, and killed results.
|
|
61
63
|
Reject survived, skipped, duplicate, conflicting, stale, or unlinked mutants.
|
|
62
|
-
|
|
64
|
+
Run `mutation doctor` for language-aware local probes and remediation;
|
|
65
|
+
musubix3 validates evidence and does not bundle a mutation engine.
|
|
63
66
|
Missing required tools, commands, artifacts or evidence block readiness.
|
|
64
67
|
5. Use native review/security-review as needed, recording their findings
|
|
65
68
|
separately from machine evidence. Never fabricate review or test results.
|
|
@@ -72,4 +75,4 @@ completed` exactly once.
|
|
|
72
75
|
Ensure the baseline protects CI-required mode, strict OIDC and key binding.
|
|
73
76
|
Never store a private key or overstate OIDC as proof of arbitrary runner work.
|
|
74
77
|
7. Run `npx musubix3 status --json`; stale evidence must be refreshed. Report
|
|
75
|
-
limitations and residual risks before handoff
|
|
78
|
+
limitations and residual risks before handoff; no resident watcher or REPL.
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.1.2 - 2026-09-07
|
|
6
|
+
|
|
7
|
+
- Exclude conventional `.venv` and `venv` Python environments identified by a
|
|
8
|
+
regular `pyvenv.cfg` file from project snapshots and Code Graph indexing,
|
|
9
|
+
without allowing the marker to hide arbitrary source directories.
|
|
10
|
+
- Add configurable `tdd.redPreflightCommands` so formatters and other plain
|
|
11
|
+
preflight commands complete before a Red test fingerprint is captured.
|
|
12
|
+
- Add explicit `custom`, `minimal`, `recommended`, and `release` quality
|
|
13
|
+
profiles with fail-closed requirements for stronger profiles.
|
|
14
|
+
- Accept bounded timestamp skew for causally ordered concurrent Copilot tool
|
|
15
|
+
events in strict workflow verification, with a policy-protected
|
|
16
|
+
`workflow.maxEventSkewMs` limit.
|
|
17
|
+
- Add `mutation doctor` with language-aware local engine probes, attempted
|
|
18
|
+
commands, and actionable configuration recommendations.
|
|
19
|
+
|
|
5
20
|
## 0.1.1 - 2026-09-07
|
|
6
21
|
|
|
7
22
|
- Preserve fail-closed input-stability checks while reporting every added,
|
package/README-ja.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# musubix3
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**最新公開版 v0.1.2 · GitHub Copilot CLI 専用 · Node.js ≥20 · TypeScript · MIT**
|
|
4
4
|
|
|
5
5
|
[English](README.md)
|
|
6
6
|
|
|
@@ -19,16 +19,16 @@ ID の接続や SAT 判定だけで、実装の正しさを保証するもので
|
|
|
19
19
|
対象プロジェクトで、公開済みパッケージを実行します。
|
|
20
20
|
|
|
21
21
|
```sh
|
|
22
|
-
npx musubix3@0.1.
|
|
23
|
-
npx musubix3@0.1.
|
|
24
|
-
npx musubix3@0.1.
|
|
22
|
+
npx musubix3@0.1.2 --version
|
|
23
|
+
npx musubix3@0.1.2 init --dry-run
|
|
24
|
+
npx musubix3@0.1.2 init
|
|
25
25
|
copilot
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
バージョンを固定してプロジェクトへインストールする場合は、次を実行します。
|
|
29
29
|
|
|
30
30
|
```sh
|
|
31
|
-
npm install --save-dev --save-exact musubix3@0.1.
|
|
31
|
+
npm install --save-dev --save-exact musubix3@0.1.2
|
|
32
32
|
npx --no-install musubix3 --version
|
|
33
33
|
npx --no-install musubix3 init --dry-run
|
|
34
34
|
npx --no-install musubix3 init
|
|
@@ -176,6 +176,7 @@ npx musubix3 status --json
|
|
|
176
176
|
| `knowledge query <text> [--limit 10]` | TF-IDF/cosine のランキングと陳腐化情報 |
|
|
177
177
|
| `formal generate <file> [--format both\|smt2\|lean]` | SHA-256付きの再現可能なsolver入力生成 |
|
|
178
178
|
| `formal doctor` | Z3、Lean、`lake env lean` の存在とバージョン確認 |
|
|
179
|
+
| `mutation doctor` | 言語別のローカルmutation engine確認と導入推奨 |
|
|
179
180
|
| `formal check <file> [--solver auto\|none\|z3\|lean]` | 明示的なBoolean・条件・数値・時間・状態遷移モデルを検査 |
|
|
180
181
|
| `model-correspondence validate` | Formal JSON→生成trace→正本passing testの証拠を再検証 |
|
|
181
182
|
| `evidence refresh [--changed]` | 同じfail-closed gate pipelineで派生証拠を再生成 |
|
|
@@ -327,6 +328,7 @@ version: 1.0.0
|
|
|
327
328
|
{
|
|
328
329
|
"schemaVersion": 1,
|
|
329
330
|
"language": "auto",
|
|
331
|
+
"qualityProfile": "custom",
|
|
330
332
|
"commands": [
|
|
331
333
|
{ "name": "typecheck", "command": "npm", "args": ["run", "typecheck"], "required": true, "timeoutMs": 120000 },
|
|
332
334
|
{
|
|
@@ -342,10 +344,12 @@ version: 1.0.0
|
|
|
342
344
|
"thresholds": { "design": 1, "implementation": 1, "tests": 1 },
|
|
343
345
|
"formal": { "solver": "none", "minModeledFraction": 0, "timeoutMs": 12000 },
|
|
344
346
|
"mutation": { "mode": "compatible" },
|
|
347
|
+
"tdd": { "redPreflightCommands": [] },
|
|
345
348
|
"workflow": {
|
|
346
349
|
"mode": "compatible",
|
|
347
350
|
"maxAgeSeconds": 3600,
|
|
348
|
-
"maxFutureSkewSeconds": 60
|
|
351
|
+
"maxFutureSkewSeconds": 60,
|
|
352
|
+
"maxEventSkewMs": 1000
|
|
349
353
|
},
|
|
350
354
|
"attestation": {
|
|
351
355
|
"mode": "local",
|
|
@@ -366,6 +370,15 @@ version: 1.0.0
|
|
|
366
370
|
|
|
367
371
|
未知の設定キー、不正な閾値、重複コマンドはエラーです。
|
|
368
372
|
glob は `*` / `**` / `?` に対応し、外部依存は `npm:` 接頭辞で表現します。
|
|
373
|
+
`qualityProfile`の既定値は`custom`です。`minimal`はSDDの基本gate、
|
|
374
|
+
`recommended`はstrict Code Graph、TDD、構造化test identityも要求し、
|
|
375
|
+
`release`はformal、mutation、workflow、変更、performance、CI attestationを
|
|
376
|
+
含む完全なrelease checkを要求します。不足設定や弱い設定を証拠で補ったことにはしません。
|
|
377
|
+
`tdd.redPreflightCommands`にはformatter等のplain command名を指定でき、
|
|
378
|
+
Redのtest fingerprintを取得する前に成功が必須です。
|
|
379
|
+
通常ファイルの`pyvenv.cfg`を含む`.venv`と`venv`はsnapshotと
|
|
380
|
+
Code Graphから除外されますが、任意のsource directoryにmarkerを置いても
|
|
381
|
+
除外されません。
|
|
369
382
|
`codeGraph.mode` の既定値は `compatible` で、未解決の計算された
|
|
370
383
|
`import()` / `require()` は警告です。`strict` にするとグラフゲートを阻止する
|
|
371
384
|
エラーになります。信頼済みbaselineが `strict` の場合、`compatible` への
|
|
@@ -386,7 +399,9 @@ command reportでpassしたことを要求します。欠落・改変・stale・
|
|
|
386
399
|
|
|
387
400
|
`.musubix/policy-baseline.json` は必須チェック、閾値、アーキテクチャ、
|
|
388
401
|
Formalポリシー、mutation mode、workflow strict/session/freshness、CI必須attestation、
|
|
389
|
-
strict OIDC identity/key binding
|
|
402
|
+
strict OIDC identity/key binding、必須コマンド名の最低条件です。Red preflightを
|
|
403
|
+
要求するbaselineは正規化済み`commands`定義も保持し、同名formatterへの
|
|
404
|
+
すり替えを拒否します。弱体化は拒否され、
|
|
390
405
|
変更ゲート中のbaseline変更には独立承認が必要です。CODEOWNERS等で保護してください。
|
|
391
406
|
|
|
392
407
|
**信頼した設定だけを実行してください。** ゲートは環境変数を継承し、シェルを
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# musubix3
|
|
2
2
|
|
|
3
|
-
**v0.1.
|
|
3
|
+
**Latest published v0.1.2 · GitHub Copilot CLI only · Node.js ≥20 · TypeScript · MIT**
|
|
4
4
|
|
|
5
5
|
[日本語](README-ja.md)
|
|
6
6
|
|
|
@@ -21,16 +21,16 @@ or requirements are satisfiable.
|
|
|
21
21
|
Run the published package from the target project:
|
|
22
22
|
|
|
23
23
|
```sh
|
|
24
|
-
npx musubix3@0.1.
|
|
25
|
-
npx musubix3@0.1.
|
|
26
|
-
npx musubix3@0.1.
|
|
24
|
+
npx musubix3@0.1.2 --version
|
|
25
|
+
npx musubix3@0.1.2 init --dry-run
|
|
26
|
+
npx musubix3@0.1.2 init
|
|
27
27
|
copilot
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
For a reproducible project-local installation:
|
|
31
31
|
|
|
32
32
|
```sh
|
|
33
|
-
npm install --save-dev --save-exact musubix3@0.1.
|
|
33
|
+
npm install --save-dev --save-exact musubix3@0.1.2
|
|
34
34
|
npx --no-install musubix3 --version
|
|
35
35
|
npx --no-install musubix3 init --dry-run
|
|
36
36
|
npx --no-install musubix3 init
|
|
@@ -184,6 +184,7 @@ validation/gate or requested solver failure, **2** usage, I/O or malformed confi
|
|
|
184
184
|
| `knowledge query <text> [--limit 10]` | Deterministic TF-IDF/cosine results and staleness flag |
|
|
185
185
|
| `formal generate <file> [--format both\|smt2\|lean]` | Reproducible solver inputs with SHA-256 evidence |
|
|
186
186
|
| `formal doctor` | Probe Z3, Lean, and `lake env lean` availability and versions |
|
|
187
|
+
| `mutation doctor` | Probe language-aware local mutation engines and show setup recommendations |
|
|
187
188
|
| `formal check <file> [--solver auto\|none\|z3\|lean]` | Check the explicit Boolean/conditional/numeric/temporal/transition model |
|
|
188
189
|
| `model-correspondence validate` | Revalidate Formal JSON → generated trace → authoritative passing test evidence |
|
|
189
190
|
| `evidence refresh [--changed]` | Regenerate derived evidence through the same fail-closed gate pipeline |
|
|
@@ -328,6 +329,7 @@ Example `.musubix/config.json` (adapt command arguments to your own project):
|
|
|
328
329
|
{
|
|
329
330
|
"schemaVersion": 1,
|
|
330
331
|
"language": "auto",
|
|
332
|
+
"qualityProfile": "custom",
|
|
331
333
|
"commands": [
|
|
332
334
|
{ "name": "typecheck", "command": "npm", "args": ["run", "typecheck"], "required": true, "timeoutMs": 120000 },
|
|
333
335
|
{
|
|
@@ -343,10 +345,12 @@ Example `.musubix/config.json` (adapt command arguments to your own project):
|
|
|
343
345
|
"thresholds": { "design": 1, "implementation": 1, "tests": 1 },
|
|
344
346
|
"formal": { "solver": "none", "minModeledFraction": 0, "timeoutMs": 12000 },
|
|
345
347
|
"mutation": { "mode": "compatible" },
|
|
348
|
+
"tdd": { "redPreflightCommands": [] },
|
|
346
349
|
"workflow": {
|
|
347
350
|
"mode": "compatible",
|
|
348
351
|
"maxAgeSeconds": 3600,
|
|
349
|
-
"maxFutureSkewSeconds": 60
|
|
352
|
+
"maxFutureSkewSeconds": 60,
|
|
353
|
+
"maxEventSkewMs": 1000
|
|
350
354
|
},
|
|
351
355
|
"attestation": {
|
|
352
356
|
"mode": "local",
|
|
@@ -367,6 +371,16 @@ Example `.musubix/config.json` (adapt command arguments to your own project):
|
|
|
367
371
|
|
|
368
372
|
Config is validated strictly; misspelled keys, invalid bounds and duplicate
|
|
369
373
|
commands fail closed. Globs support `*`, `**`, `?`; external imports use `npm:`.
|
|
374
|
+
`qualityProfile` is `custom` by default. `minimal` preserves the core SDD gate,
|
|
375
|
+
`recommended` also requires strict Code Graph, TDD and structured test
|
|
376
|
+
identities, and `release` requires the complete formal, mutation, workflow,
|
|
377
|
+
change, performance and CI-attestation checks. Stronger profiles reject missing
|
|
378
|
+
or weakened settings rather than silently filling in evidence.
|
|
379
|
+
Use `tdd.redPreflightCommands` to reference plain configured formatter commands;
|
|
380
|
+
they must pass before Red captures the authoritative test fingerprint.
|
|
381
|
+
Conventional `.venv` and `venv` Python environments containing a regular
|
|
382
|
+
`pyvenv.cfg` are excluded from source snapshots and Code Graph indexing.
|
|
383
|
+
Arbitrary source directories containing that marker remain tracked.
|
|
370
384
|
`codeGraph.mode` defaults to `compatible`, where unresolved computed
|
|
371
385
|
`import()`/`require()` calls remain warnings. Set it to `strict` to make those
|
|
372
386
|
diagnostics gate-blocking errors. A trusted strict policy baseline prevents
|
|
@@ -391,7 +405,9 @@ diagnostic codes are stable English; human status labels include Japanese.
|
|
|
391
405
|
`.musubix/policy-baseline.json` records minimum required checks, coverage,
|
|
392
406
|
architecture, formal policy, mutation mode, workflow strict/session/freshness settings,
|
|
393
407
|
CI-required attestation and strict OIDC identity/key binding, and required
|
|
394
|
-
command names.
|
|
408
|
+
command names. A baseline that requires TDD Red preflights must also include
|
|
409
|
+
their normalized `commands` definitions, which prevents replacing a trusted
|
|
410
|
+
formatter while retaining its name. Weakening is rejected; changing the baseline in `gate --changed`
|
|
395
411
|
requires independent approval. Protect the baseline with review/CODEOWNERS.
|
|
396
412
|
|
|
397
413
|
**Only run trusted configuration**: gates execute its commands with inherited
|
|
@@ -55,6 +55,7 @@ export interface WorkflowConfig {
|
|
|
55
55
|
expectedSessionId?: string;
|
|
56
56
|
maxAgeSeconds?: number;
|
|
57
57
|
maxFutureSkewSeconds?: number;
|
|
58
|
+
maxEventSkewMs?: number;
|
|
58
59
|
}
|
|
59
60
|
export interface CodeGraphConfig {
|
|
60
61
|
mode: 'compatible' | 'strict';
|
|
@@ -62,9 +63,14 @@ export interface CodeGraphConfig {
|
|
|
62
63
|
export interface MutationConfig {
|
|
63
64
|
mode: 'compatible' | 'strict';
|
|
64
65
|
}
|
|
66
|
+
export interface TddConfig {
|
|
67
|
+
redPreflightCommands: string[];
|
|
68
|
+
}
|
|
69
|
+
export type QualityProfile = 'custom' | 'minimal' | 'recommended' | 'release';
|
|
65
70
|
export interface Config {
|
|
66
71
|
schemaVersion: 1;
|
|
67
72
|
language: 'auto' | 'en' | 'ja';
|
|
73
|
+
qualityProfile: QualityProfile;
|
|
68
74
|
commands: CommandConfig[];
|
|
69
75
|
requiredChecks: string[];
|
|
70
76
|
thresholds: {
|
|
@@ -79,17 +85,21 @@ export interface Config {
|
|
|
79
85
|
codeGraph: CodeGraphConfig;
|
|
80
86
|
formal: FormalConfig;
|
|
81
87
|
mutation: MutationConfig;
|
|
88
|
+
tdd: TddConfig;
|
|
82
89
|
workflow: WorkflowConfig;
|
|
83
90
|
attestation: AttestationConfig;
|
|
84
91
|
}
|
|
85
92
|
export interface PolicyBaseline {
|
|
86
93
|
schemaVersion: 1;
|
|
94
|
+
qualityProfile: QualityProfile;
|
|
95
|
+
commands: CommandConfig[];
|
|
87
96
|
requiredChecks: string[];
|
|
88
97
|
thresholds: Config['thresholds'];
|
|
89
98
|
architecture: Config['architecture'];
|
|
90
99
|
codeGraph: Config['codeGraph'];
|
|
91
100
|
formal: Pick<FormalConfig, 'solver' | 'minModeledFraction'>;
|
|
92
101
|
mutation: MutationConfig;
|
|
102
|
+
tdd: TddConfig;
|
|
93
103
|
workflow: WorkflowConfig;
|
|
94
104
|
attestation: AttestationConfig;
|
|
95
105
|
requiredCommands: string[];
|
|
@@ -4,6 +4,7 @@ export const checkNames = ['requirements', 'design', 'constitution', 'trace', 'g
|
|
|
4
4
|
export const defaultConfig = {
|
|
5
5
|
schemaVersion: 1,
|
|
6
6
|
language: 'auto',
|
|
7
|
+
qualityProfile: 'custom',
|
|
7
8
|
commands: [],
|
|
8
9
|
requiredChecks: ['requirements', 'design', 'constitution', 'trace', 'graph', 'commands'],
|
|
9
10
|
thresholds: { design: 1, implementation: 1, tests: 1 },
|
|
@@ -11,7 +12,8 @@ export const defaultConfig = {
|
|
|
11
12
|
codeGraph: { mode: 'compatible' },
|
|
12
13
|
formal: { solver: 'none', minModeledFraction: 0, timeoutMs: 12_000 },
|
|
13
14
|
mutation: { mode: 'compatible' },
|
|
14
|
-
|
|
15
|
+
tdd: { redPreflightCommands: [] },
|
|
16
|
+
workflow: { mode: 'compatible', maxAgeSeconds: 3600, maxFutureSkewSeconds: 60, maxEventSkewMs: 1000 },
|
|
15
17
|
attestation: {
|
|
16
18
|
mode: 'local',
|
|
17
19
|
maxAgeSeconds: 3600,
|
|
@@ -22,12 +24,15 @@ export const defaultConfig = {
|
|
|
22
24
|
};
|
|
23
25
|
export const defaultPolicyBaseline = {
|
|
24
26
|
schemaVersion: 1,
|
|
27
|
+
qualityProfile: defaultConfig.qualityProfile,
|
|
28
|
+
commands: [],
|
|
25
29
|
requiredChecks: [...defaultConfig.requiredChecks],
|
|
26
30
|
thresholds: { ...defaultConfig.thresholds },
|
|
27
31
|
architecture: { forbidCycles: defaultConfig.architecture.forbidCycles, rules: [...defaultConfig.architecture.rules] },
|
|
28
32
|
codeGraph: { ...defaultConfig.codeGraph },
|
|
29
33
|
formal: { solver: defaultConfig.formal.solver, minModeledFraction: defaultConfig.formal.minModeledFraction },
|
|
30
34
|
mutation: { ...defaultConfig.mutation },
|
|
35
|
+
tdd: { redPreflightCommands: [...defaultConfig.tdd.redPreflightCommands] },
|
|
31
36
|
workflow: { ...defaultConfig.workflow },
|
|
32
37
|
attestation: {
|
|
33
38
|
...defaultConfig.attestation,
|
|
@@ -71,14 +76,51 @@ function validateAdapterArgs(adapter, args, name) {
|
|
|
71
76
|
throw new Error(`command ${name} args contain adapter-owned argument ${conflict}; remove it because the ${adapter} adapter adds targeted test/report arguments.`);
|
|
72
77
|
}
|
|
73
78
|
}
|
|
79
|
+
const profileRequiredChecks = {
|
|
80
|
+
minimal: ['requirements', 'design', 'constitution', 'trace', 'graph', 'commands'],
|
|
81
|
+
recommended: ['requirements', 'design', 'constitution', 'trace', 'graph', 'commands', 'test-identities', 'tdd'],
|
|
82
|
+
release: [
|
|
83
|
+
'requirements', 'design', 'constitution', 'trace', 'graph', 'formal', 'model-correspondence',
|
|
84
|
+
'mutation', 'workflow', 'tdd', 'change-history', 'change-completeness', 'performance',
|
|
85
|
+
'attestation', 'test-identities', 'commands',
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
function validateQualityProfile(config) {
|
|
89
|
+
if (config.qualityProfile === 'custom')
|
|
90
|
+
return;
|
|
91
|
+
const missing = profileRequiredChecks[config.qualityProfile]
|
|
92
|
+
.filter((check) => !config.requiredChecks.includes(check));
|
|
93
|
+
if (missing.length) {
|
|
94
|
+
throw new Error(`qualityProfile ${config.qualityProfile} requires checks: ${missing.join(', ')}.`);
|
|
95
|
+
}
|
|
96
|
+
if (config.qualityProfile !== 'minimal' && config.codeGraph.mode !== 'strict') {
|
|
97
|
+
throw new Error(`qualityProfile ${config.qualityProfile} requires codeGraph.mode strict.`);
|
|
98
|
+
}
|
|
99
|
+
if (config.qualityProfile === 'release') {
|
|
100
|
+
if (config.formal.solver === 'none' || config.formal.minModeledFraction <= 0) {
|
|
101
|
+
throw new Error('qualityProfile release requires a formal solver and positive modeled coverage.');
|
|
102
|
+
}
|
|
103
|
+
if (config.mutation.mode !== 'strict')
|
|
104
|
+
throw new Error('qualityProfile release requires mutation.mode strict.');
|
|
105
|
+
if (config.workflow.mode !== 'strict')
|
|
106
|
+
throw new Error('qualityProfile release requires workflow.mode strict.');
|
|
107
|
+
if (config.attestation.mode !== 'ci-required') {
|
|
108
|
+
throw new Error('qualityProfile release requires attestation.mode ci-required.');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
74
112
|
export function parseConfig(input) {
|
|
75
113
|
const value = object(input, 'config');
|
|
76
|
-
keys(value, ['schemaVersion', 'language', 'commands', 'requiredChecks', 'thresholds', 'architecture', 'codeGraph', 'formal', 'mutation', 'workflow', 'attestation'], 'config');
|
|
114
|
+
keys(value, ['schemaVersion', 'language', 'qualityProfile', 'commands', 'requiredChecks', 'thresholds', 'architecture', 'codeGraph', 'formal', 'mutation', 'tdd', 'workflow', 'attestation'], 'config');
|
|
77
115
|
if (value.schemaVersion !== 1)
|
|
78
116
|
throw new Error('Unsupported config schemaVersion; expected 1.');
|
|
79
117
|
const language = optional(value.language, 'auto');
|
|
80
118
|
if (!['auto', 'en', 'ja'].includes(String(language)))
|
|
81
119
|
throw new Error('language must be auto, en, or ja.');
|
|
120
|
+
const qualityProfile = optional(value.qualityProfile, defaultConfig.qualityProfile);
|
|
121
|
+
if (!['custom', 'minimal', 'recommended', 'release'].includes(String(qualityProfile))) {
|
|
122
|
+
throw new Error('qualityProfile must be custom, minimal, recommended, or release.');
|
|
123
|
+
}
|
|
82
124
|
const requiredChecks = strings(optional(value.requiredChecks, defaultConfig.requiredChecks), 'requiredChecks');
|
|
83
125
|
if (requiredChecks.some((s) => !checkNames.includes(s)))
|
|
84
126
|
throw new Error('Unknown required check.');
|
|
@@ -178,6 +220,20 @@ export function parseConfig(input) {
|
|
|
178
220
|
});
|
|
179
221
|
if (new Set(commands.map((c) => c.name)).size !== commands.length)
|
|
180
222
|
throw new Error('Command names must be unique.');
|
|
223
|
+
const rawTdd = object(optional(value.tdd, {}), 'tdd');
|
|
224
|
+
keys(rawTdd, ['redPreflightCommands'], 'tdd');
|
|
225
|
+
const redPreflightCommands = strings(optional(rawTdd.redPreflightCommands, defaultConfig.tdd.redPreflightCommands), 'tdd.redPreflightCommands');
|
|
226
|
+
if (new Set(redPreflightCommands).size !== redPreflightCommands.length) {
|
|
227
|
+
throw new Error('tdd.redPreflightCommands must not contain duplicates.');
|
|
228
|
+
}
|
|
229
|
+
for (const name of redPreflightCommands) {
|
|
230
|
+
const command = commands.find((candidate) => candidate.name === name);
|
|
231
|
+
if (!command)
|
|
232
|
+
throw new Error(`tdd.redPreflightCommands references unknown command ${name}.`);
|
|
233
|
+
if (command.adapter || command.testReport || command.tddReport || command.mutationReport) {
|
|
234
|
+
throw new Error(`TDD Red preflight command ${name} must be a plain command without test or mutation reports.`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
181
237
|
const architecture = object(optional(value.architecture, {}), 'architecture');
|
|
182
238
|
keys(architecture, ['forbidCycles', 'rules'], 'architecture');
|
|
183
239
|
if (architecture.forbidCycles !== undefined && typeof architecture.forbidCycles !== 'boolean')
|
|
@@ -218,7 +274,7 @@ export function parseConfig(input) {
|
|
|
218
274
|
throw new Error('mutation.mode must be compatible or strict.');
|
|
219
275
|
}
|
|
220
276
|
const rawWorkflow = object(optional(value.workflow, {}), 'workflow');
|
|
221
|
-
keys(rawWorkflow, ['mode', 'expectedSessionId', 'maxAgeSeconds', 'maxFutureSkewSeconds'], 'workflow');
|
|
277
|
+
keys(rawWorkflow, ['mode', 'expectedSessionId', 'maxAgeSeconds', 'maxFutureSkewSeconds', 'maxEventSkewMs'], 'workflow');
|
|
222
278
|
const workflowMode = optional(rawWorkflow.mode, defaultConfig.workflow.mode);
|
|
223
279
|
if (!['compatible', 'strict'].includes(String(workflowMode))) {
|
|
224
280
|
throw new Error('workflow.mode must be compatible or strict.');
|
|
@@ -241,6 +297,11 @@ export function parseConfig(input) {
|
|
|
241
297
|
|| workflowMaxFutureSkewSeconds < 0 || workflowMaxFutureSkewSeconds > 600) {
|
|
242
298
|
throw new Error('workflow.maxFutureSkewSeconds must be 0..600.');
|
|
243
299
|
}
|
|
300
|
+
const workflowMaxEventSkewMs = optional(rawWorkflow.maxEventSkewMs, defaultConfig.workflow.maxEventSkewMs);
|
|
301
|
+
if (typeof workflowMaxEventSkewMs !== 'number' || !Number.isInteger(workflowMaxEventSkewMs)
|
|
302
|
+
|| workflowMaxEventSkewMs < 0 || workflowMaxEventSkewMs > 60_000) {
|
|
303
|
+
throw new Error('workflow.maxEventSkewMs must be 0..60000.');
|
|
304
|
+
}
|
|
244
305
|
const rawAttestation = object(optional(value.attestation, {}), 'attestation');
|
|
245
306
|
keys(rawAttestation, [
|
|
246
307
|
'mode', 'repository', 'maxAgeSeconds', 'maxFutureSkewSeconds', 'trustedPublicKeys', 'githubOidc',
|
|
@@ -307,17 +368,24 @@ export function parseConfig(input) {
|
|
|
307
368
|
&& rawAttestation.repository !== rawGithubOidc.repository) {
|
|
308
369
|
throw new Error('attestation.githubOidc.repository must match attestation.repository.');
|
|
309
370
|
}
|
|
310
|
-
|
|
311
|
-
schemaVersion: 1,
|
|
371
|
+
const config = {
|
|
372
|
+
schemaVersion: 1,
|
|
373
|
+
language: language,
|
|
374
|
+
qualityProfile: qualityProfile,
|
|
375
|
+
commands,
|
|
376
|
+
requiredChecks,
|
|
377
|
+
thresholds,
|
|
312
378
|
architecture: { forbidCycles: architecture.forbidCycles !== false, rules },
|
|
313
379
|
codeGraph: { mode: codeGraphMode },
|
|
314
380
|
formal: { solver: solver, minModeledFraction, timeoutMs: formalTimeoutMs },
|
|
315
381
|
mutation: { mode: mutationMode },
|
|
382
|
+
tdd: { redPreflightCommands },
|
|
316
383
|
workflow: {
|
|
317
384
|
mode: workflowMode,
|
|
318
385
|
...(typeof rawWorkflow.expectedSessionId === 'string' ? { expectedSessionId: rawWorkflow.expectedSessionId } : {}),
|
|
319
386
|
maxAgeSeconds: workflowMaxAgeSeconds,
|
|
320
387
|
maxFutureSkewSeconds: workflowMaxFutureSkewSeconds,
|
|
388
|
+
maxEventSkewMs: workflowMaxEventSkewMs,
|
|
321
389
|
},
|
|
322
390
|
attestation: {
|
|
323
391
|
mode: mode,
|
|
@@ -338,6 +406,8 @@ export function parseConfig(input) {
|
|
|
338
406
|
: { mode: 'off' },
|
|
339
407
|
},
|
|
340
408
|
};
|
|
409
|
+
validateQualityProfile(config);
|
|
410
|
+
return config;
|
|
341
411
|
}
|
|
342
412
|
export async function loadConfig(root) {
|
|
343
413
|
const path = '.musubix/config.json';
|
|
@@ -348,31 +418,36 @@ export async function loadConfig(root) {
|
|
|
348
418
|
export function parsePolicyBaseline(input) {
|
|
349
419
|
const value = object(input, 'policy baseline');
|
|
350
420
|
keys(value, [
|
|
351
|
-
'schemaVersion', 'requiredChecks', 'thresholds', 'architecture', 'codeGraph', 'formal',
|
|
352
|
-
'mutation', 'workflow', 'attestation', 'requiredCommands',
|
|
421
|
+
'schemaVersion', 'qualityProfile', 'commands', 'requiredChecks', 'thresholds', 'architecture', 'codeGraph', 'formal',
|
|
422
|
+
'mutation', 'tdd', 'workflow', 'attestation', 'requiredCommands',
|
|
353
423
|
], 'policy baseline');
|
|
354
424
|
if (value.schemaVersion !== 1)
|
|
355
425
|
throw new Error('Unsupported policy baseline schemaVersion; expected 1.');
|
|
356
426
|
const parsed = parseConfig({
|
|
357
427
|
schemaVersion: 1,
|
|
428
|
+
qualityProfile: value.qualityProfile,
|
|
358
429
|
requiredChecks: value.requiredChecks,
|
|
359
430
|
thresholds: value.thresholds,
|
|
360
431
|
architecture: value.architecture,
|
|
361
432
|
codeGraph: value.codeGraph,
|
|
362
433
|
formal: value.formal,
|
|
363
434
|
mutation: value.mutation,
|
|
435
|
+
tdd: value.tdd,
|
|
364
436
|
workflow: value.workflow,
|
|
365
437
|
attestation: value.attestation,
|
|
366
|
-
commands:
|
|
438
|
+
commands: value.commands,
|
|
367
439
|
});
|
|
368
440
|
return {
|
|
369
441
|
schemaVersion: 1,
|
|
442
|
+
qualityProfile: parsed.qualityProfile,
|
|
443
|
+
commands: parsed.commands,
|
|
370
444
|
requiredChecks: parsed.requiredChecks,
|
|
371
445
|
thresholds: parsed.thresholds,
|
|
372
446
|
architecture: parsed.architecture,
|
|
373
447
|
codeGraph: parsed.codeGraph,
|
|
374
448
|
formal: { solver: parsed.formal.solver, minModeledFraction: parsed.formal.minModeledFraction },
|
|
375
449
|
mutation: parsed.mutation,
|
|
450
|
+
tdd: parsed.tdd,
|
|
376
451
|
workflow: parsed.workflow,
|
|
377
452
|
attestation: parsed.attestation,
|
|
378
453
|
requiredCommands: strings(optional(value.requiredCommands, []), 'requiredCommands'),
|
|
@@ -386,6 +461,10 @@ export async function loadPolicyBaseline(root) {
|
|
|
386
461
|
}
|
|
387
462
|
export function policyDiagnostics(config, baseline, changed = null) {
|
|
388
463
|
const diagnostics = [];
|
|
464
|
+
const profileRank = { custom: 0, minimal: 1, recommended: 2, release: 3 };
|
|
465
|
+
if (profileRank[config.qualityProfile] < profileRank[baseline.qualityProfile]) {
|
|
466
|
+
diagnostics.push(error('POLICY_QUALITY_PROFILE', `qualityProfile cannot be weakened below ${baseline.qualityProfile}.`));
|
|
467
|
+
}
|
|
389
468
|
for (const check of baseline.requiredChecks) {
|
|
390
469
|
if (!config.requiredChecks.includes(check))
|
|
391
470
|
diagnostics.push(error('POLICY_REQUIRED_CHECK', `Required check ${check} was removed from the trusted baseline.`));
|
|
@@ -413,6 +492,16 @@ export function policyDiagnostics(config, baseline, changed = null) {
|
|
|
413
492
|
if (baseline.mutation.mode === 'strict' && config.mutation.mode !== 'strict') {
|
|
414
493
|
diagnostics.push(error('POLICY_MUTATION_MODE', 'mutation.mode cannot be weakened below strict as required by the trusted baseline.'));
|
|
415
494
|
}
|
|
495
|
+
for (const command of baseline.tdd.redPreflightCommands) {
|
|
496
|
+
const baselineCommand = baseline.commands.find((candidate) => candidate.name === command);
|
|
497
|
+
const configuredCommand = config.commands.find((candidate) => candidate.name === command);
|
|
498
|
+
if (!config.tdd.redPreflightCommands.includes(command) || !configuredCommand) {
|
|
499
|
+
diagnostics.push(error('POLICY_TDD_PREFLIGHT', `TDD Red preflight command ${command} was removed from the trusted baseline.`));
|
|
500
|
+
}
|
|
501
|
+
else if (baselineCommand && JSON.stringify(configuredCommand) !== JSON.stringify(baselineCommand)) {
|
|
502
|
+
diagnostics.push(error('POLICY_TDD_PREFLIGHT', `TDD Red preflight command ${command} differs from the trusted baseline definition.`));
|
|
503
|
+
}
|
|
504
|
+
}
|
|
416
505
|
if (baseline.workflow.mode === 'strict') {
|
|
417
506
|
if (config.workflow.mode !== 'strict') {
|
|
418
507
|
diagnostics.push(error('POLICY_WORKFLOW_MODE', 'workflow.mode cannot be weakened below strict as required by the trusted baseline.'));
|
|
@@ -427,6 +516,9 @@ export function policyDiagnostics(config, baseline, changed = null) {
|
|
|
427
516
|
if ((config.workflow.maxFutureSkewSeconds ?? 60) > (baseline.workflow.maxFutureSkewSeconds ?? 60)) {
|
|
428
517
|
diagnostics.push(error('POLICY_WORKFLOW_FUTURE_SKEW', 'workflow.maxFutureSkewSeconds is weaker than the trusted baseline.'));
|
|
429
518
|
}
|
|
519
|
+
if ((config.workflow.maxEventSkewMs ?? 1000) > (baseline.workflow.maxEventSkewMs ?? 1000)) {
|
|
520
|
+
diagnostics.push(error('POLICY_WORKFLOW_EVENT_SKEW', 'workflow.maxEventSkewMs is weaker than the trusted baseline.'));
|
|
521
|
+
}
|
|
430
522
|
}
|
|
431
523
|
if (baseline.attestation.mode === 'ci-required') {
|
|
432
524
|
if (config.attestation.mode !== 'ci-required') {
|