@ionivetech/mugiwara 0.8.1 → 0.8.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.kimi-plugin/plugin.json +1 -1
- package/content/skills/mugiwara-orchestration/SKILL.md +2 -2
- package/content/skills/mugiwara-workflow/SKILL.md +19 -19
- package/dist/mugiwara.js +143 -36
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/scripts/gate-selftest.ts +156 -1
- package/scripts/lane-base.ts +16 -0
- package/scripts/lane.sh +5 -1
- package/scripts/lib/lane-base.sh +1 -1
- package/scripts/savepoint.sh +28 -5
- package/src/cli.ts +60 -18
- package/src/continue.ts +7 -1
- package/src/cost.ts +1 -1
- package/src/installer.ts +27 -4
- package/src/integrity.ts +41 -10
- package/src/policy.ts +17 -2
package/src/integrity.ts
CHANGED
|
@@ -162,23 +162,54 @@ export function checkTrail(missionDir: string, projectRoot: string): IntegrityIs
|
|
|
162
162
|
|
|
163
163
|
// 3: evidence entries recorded as repo paths must exist
|
|
164
164
|
const evidencePaths: string[] = [];
|
|
165
|
-
|
|
166
|
-
|
|
165
|
+
// Solo layout writes state.json; team layout writes <member>.json per member.
|
|
166
|
+
// Reading only state.json left the evidence gate dead on the team path (B2).
|
|
167
|
+
const stateFiles = existsSync(missionDir)
|
|
168
|
+
? readdirSync(missionDir)
|
|
169
|
+
.filter((n) => n.endsWith('.json') && n !== 'continue.json' && !n.startsWith('continue-'))
|
|
170
|
+
.sort()
|
|
171
|
+
: [];
|
|
172
|
+
for (const name of stateFiles) {
|
|
173
|
+
const evidenceFile = join(missionDir, name);
|
|
167
174
|
try {
|
|
168
175
|
const s = JSON.parse(readFileSync(evidenceFile, 'utf8')) as { evidence?: unknown };
|
|
169
|
-
if (Array.isArray(s.evidence))
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
176
|
+
if (!Array.isArray(s.evidence)) continue;
|
|
177
|
+
for (const e of s.evidence) {
|
|
178
|
+
if (typeof e !== 'string' || !e.trim()) continue;
|
|
179
|
+
evidencePaths.push(e);
|
|
180
|
+
const cand = join(projectRoot, e);
|
|
181
|
+
if (!isAbsolute(e) && !existsSync(cand) && !existsSync(join(missionDir, e))) {
|
|
182
|
+
issues.push({ kind: 'evidence', detail: `${name} evidence "${e}" does not exist` });
|
|
177
183
|
}
|
|
178
184
|
}
|
|
179
185
|
} catch { /* corrupt state — the state reader owns that error */ }
|
|
180
186
|
}
|
|
181
187
|
|
|
188
|
+
// Iron Law: no evidence = not complete. Absent evidence is a different failure
|
|
189
|
+
// from thin evidence, and previously went unreported entirely. (B7)
|
|
190
|
+
if (evidencePaths.length === 0) {
|
|
191
|
+
let severity: 'warn' | 'block' = 'warn';
|
|
192
|
+
try {
|
|
193
|
+
const policy = loadPolicy(projectRoot);
|
|
194
|
+
const lanes = (policy as unknown as { evidence?: { require_nonempty_for_lanes?: string[] } })?.evidence?.require_nonempty_for_lanes;
|
|
195
|
+
if (Array.isArray(lanes) && lanes.length) {
|
|
196
|
+
const stateLanes = new Set<string>();
|
|
197
|
+
for (const name of stateFiles) {
|
|
198
|
+
try {
|
|
199
|
+
const s = JSON.parse(readFileSync(join(missionDir, name), 'utf8')) as { lane?: unknown };
|
|
200
|
+
if (typeof s.lane === 'string') stateLanes.add(s.lane);
|
|
201
|
+
} catch { /* ignore */ }
|
|
202
|
+
}
|
|
203
|
+
if ([...stateLanes].some((l) => lanes.includes(l))) severity = 'block';
|
|
204
|
+
}
|
|
205
|
+
} catch { /* policy read failure -> keep warn */ }
|
|
206
|
+
issues.push({
|
|
207
|
+
kind: 'evidence',
|
|
208
|
+
severity,
|
|
209
|
+
detail: 'mission declares no evidence — closing with zero recorded checks',
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
182
213
|
// 4: evidence-content spot check (T7): a PASS verdict that cites an evidence
|
|
183
214
|
// path must point at a file that exists AND contains command-output shape
|
|
184
215
|
// (backticked command or exit-status token). Fake-but-consistent trails
|
package/src/policy.ts
CHANGED
|
@@ -19,7 +19,11 @@ export type MugiwaraPolicy = {
|
|
|
19
19
|
coverage?: { new?: number; modified?: number };
|
|
20
20
|
require_human_approval?: string[];
|
|
21
21
|
};
|
|
22
|
-
evidence?: {
|
|
22
|
+
evidence?: {
|
|
23
|
+
required?: string[];
|
|
24
|
+
/** Lanes where an empty evidence set blocks archive instead of warning. (B7) */
|
|
25
|
+
require_nonempty_for_lanes?: string[];
|
|
26
|
+
};
|
|
23
27
|
integrity?: { extra_secret_patterns?: Array<{ pattern: string; label: string; severity?: 'block' | 'warn' }> };
|
|
24
28
|
attestation?: {
|
|
25
29
|
required?: boolean;
|
|
@@ -361,7 +365,18 @@ function normalize(raw: Record<string, unknown>): MugiwaraPolicy {
|
|
|
361
365
|
out.gates.require_human_approval = strings(gates.require_human_approval);
|
|
362
366
|
}
|
|
363
367
|
const evidence = raw.evidence as Record<string, unknown> | undefined;
|
|
364
|
-
if (evidence
|
|
368
|
+
if (evidence) {
|
|
369
|
+
const ev: NonNullable<MugiwaraPolicy['evidence']> = {};
|
|
370
|
+
if (Array.isArray(evidence.required)) ev.required = strings(evidence.required);
|
|
371
|
+
else if (typeof evidence.required === 'string' && (evidence.required as string).trim().startsWith('[')) {
|
|
372
|
+
try { const p = JSON.parse(evidence.required as string); if (Array.isArray(p)) ev.required = strings(p); } catch { /* ignore */ }
|
|
373
|
+
}
|
|
374
|
+
if (Array.isArray(evidence.require_nonempty_for_lanes)) ev.require_nonempty_for_lanes = strings(evidence.require_nonempty_for_lanes);
|
|
375
|
+
else if (typeof evidence.require_nonempty_for_lanes === 'string' && (evidence.require_nonempty_for_lanes as string).trim().startsWith('[')) {
|
|
376
|
+
try { const p = JSON.parse(evidence.require_nonempty_for_lanes as string); if (Array.isArray(p)) ev.require_nonempty_for_lanes = strings(p); } catch { /* ignore */ }
|
|
377
|
+
}
|
|
378
|
+
if (ev.required || ev.require_nonempty_for_lanes) out.evidence = ev;
|
|
379
|
+
}
|
|
365
380
|
const integrity = raw.integrity as Record<string, unknown> | undefined;
|
|
366
381
|
if (integrity && Array.isArray(integrity.extra_secret_patterns)) {
|
|
367
382
|
const arr = integrity.extra_secret_patterns as unknown[];
|