mova-claude-import 0.1.1
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/workflows/ci.yml +42 -0
- package/.github/workflows/release-dry-run.yml +40 -0
- package/.github/workflows/release-publish.yml +43 -0
- package/.tmp_test_control_apply/proj/.claude/agents/example_agent.md +3 -0
- package/.tmp_test_control_apply/proj/.claude/commands/example_command.md +3 -0
- package/.tmp_test_control_apply/proj/.claude/hooks/example_hook.sh +2 -0
- package/.tmp_test_control_apply/proj/.claude/output-styles/example_style.md +3 -0
- package/.tmp_test_control_apply/proj/.claude/settings.json +30 -0
- package/.tmp_test_control_apply/proj/.claude/settings.local.example.json +3 -0
- package/.tmp_test_control_apply/proj/.mcp.json +3 -0
- package/.tmp_test_control_apply/proj/CLAUDE.md +13 -0
- package/.tmp_test_control_apply/proj/MOVA.md +3 -0
- package/.tmp_test_control_check/proj/.mcp.json +1 -0
- package/.tmp_test_control_check/proj/CLAUDE.md +1 -0
- package/.tmp_test_control_prefill/out1/claude_control_profile_v0.json +114 -0
- package/.tmp_test_control_prefill/out1/prefill_report_v0.json +13 -0
- package/.tmp_test_control_prefill/out2/claude_control_profile_v0.json +114 -0
- package/.tmp_test_control_prefill/out2/prefill_report_v0.json +13 -0
- package/.tmp_test_overlay/proj/.claude/skills/a.md +1 -0
- package/.tmp_test_overlay/proj/.mcp.json +1 -0
- package/.tmp_test_overlay/proj/CLAUDE.md +1 -0
- package/.tmp_test_profile/proj/.claude/skills/a.md +1 -0
- package/.tmp_test_profile/proj/.mcp.json +1 -0
- package/.tmp_test_profile/proj/CLAUDE.md +1 -0
- package/.tmp_test_scaffold_apply/proj/.claude/agents/example_agent.md +3 -0
- package/.tmp_test_scaffold_apply/proj/.claude/commands/example_command.md +3 -0
- package/.tmp_test_scaffold_apply/proj/.claude/hooks/example_hook.sh +2 -0
- package/.tmp_test_scaffold_apply/proj/.claude/output-styles/example_style.md +3 -0
- package/.tmp_test_scaffold_apply/proj/.claude/settings.json +30 -0
- package/.tmp_test_scaffold_apply/proj/.claude/settings.local.example.json +3 -0
- package/.tmp_test_scaffold_apply/proj/.mcp.json +3 -0
- package/.tmp_test_scaffold_apply/proj/CLAUDE.md +13 -0
- package/.tmp_test_scaffold_apply/proj/MOVA.md +3 -0
- package/.tmp_test_strict/mova/claude_import/v0/VERSION.json +10 -0
- package/.tmp_test_strict/mova/claude_import/v0/episode_import_run.json +20 -0
- package/.tmp_test_strict/mova/claude_import/v0/import_manifest.json +20 -0
- package/.tmp_test_strict/mova/claude_import/v0/input_policy_report_v0.json +32 -0
- package/.tmp_test_zip/out1/.claude/agents/example_agent.md +3 -0
- package/.tmp_test_zip/out1/.claude/commands/example_command.md +3 -0
- package/.tmp_test_zip/out1/.claude/commands/mova_context.md +4 -0
- package/.tmp_test_zip/out1/.claude/commands/mova_lint.md +4 -0
- package/.tmp_test_zip/out1/.claude/commands/mova_proof.md +6 -0
- package/.tmp_test_zip/out1/.claude/hooks/example_hook.sh +2 -0
- package/.tmp_test_zip/out1/.claude/output-styles/example_style.md +3 -0
- package/.tmp_test_zip/out1/.claude/settings.json +30 -0
- package/.tmp_test_zip/out1/.claude/settings.local.example.json +3 -0
- package/.tmp_test_zip/out1/.claude/skills/a/SKILL.md +1 -0
- package/.tmp_test_zip/out1/.claude/skills/mova-control-v0/SKILL.md +11 -0
- package/.tmp_test_zip/out1/.claude/skills/mova-layer-v0/SKILL.md +8 -0
- package/.tmp_test_zip/out1/.mcp.json +3 -0
- package/.tmp_test_zip/out1/CLAUDE.md +4 -0
- package/.tmp_test_zip/out1/MOVA.md +10 -0
- package/.tmp_test_zip/out1/export.zip +0 -0
- package/.tmp_test_zip/out1/mova/claude_import/v0/VERSION.json +10 -0
- package/.tmp_test_zip/out1/mova/claude_import/v0/contracts/instruction_profile_v0.json +8 -0
- package/.tmp_test_zip/out1/mova/claude_import/v0/contracts/mcp_servers_v0.json +4 -0
- package/.tmp_test_zip/out1/mova/claude_import/v0/contracts/skills_catalog_v0.json +11 -0
- package/.tmp_test_zip/out1/mova/claude_import/v0/episode_import_run.json +80 -0
- package/.tmp_test_zip/out1/mova/claude_import/v0/export_manifest_v0.json +32 -0
- package/.tmp_test_zip/out1/mova/claude_import/v0/import_manifest.json +33 -0
- package/.tmp_test_zip/out1/mova/claude_import/v0/input_policy_report_v0.json +38 -0
- package/.tmp_test_zip/out1/mova/claude_import/v0/lint_report_v0.json +6 -0
- package/.tmp_test_zip/out1/mova/claude_import/v0/redaction_report.json +4 -0
- package/.tmp_test_zip/out2/.claude/agents/example_agent.md +3 -0
- package/.tmp_test_zip/out2/.claude/commands/example_command.md +3 -0
- package/.tmp_test_zip/out2/.claude/commands/mova_context.md +4 -0
- package/.tmp_test_zip/out2/.claude/commands/mova_lint.md +4 -0
- package/.tmp_test_zip/out2/.claude/commands/mova_proof.md +6 -0
- package/.tmp_test_zip/out2/.claude/hooks/example_hook.sh +2 -0
- package/.tmp_test_zip/out2/.claude/output-styles/example_style.md +3 -0
- package/.tmp_test_zip/out2/.claude/settings.json +30 -0
- package/.tmp_test_zip/out2/.claude/settings.local.example.json +3 -0
- package/.tmp_test_zip/out2/.claude/skills/a/SKILL.md +1 -0
- package/.tmp_test_zip/out2/.claude/skills/mova-control-v0/SKILL.md +11 -0
- package/.tmp_test_zip/out2/.claude/skills/mova-layer-v0/SKILL.md +8 -0
- package/.tmp_test_zip/out2/.mcp.json +3 -0
- package/.tmp_test_zip/out2/CLAUDE.md +4 -0
- package/.tmp_test_zip/out2/MOVA.md +10 -0
- package/.tmp_test_zip/out2/export.zip +0 -0
- package/.tmp_test_zip/out2/mova/claude_import/v0/VERSION.json +10 -0
- package/.tmp_test_zip/out2/mova/claude_import/v0/contracts/instruction_profile_v0.json +8 -0
- package/.tmp_test_zip/out2/mova/claude_import/v0/contracts/mcp_servers_v0.json +4 -0
- package/.tmp_test_zip/out2/mova/claude_import/v0/contracts/skills_catalog_v0.json +11 -0
- package/.tmp_test_zip/out2/mova/claude_import/v0/episode_import_run.json +80 -0
- package/.tmp_test_zip/out2/mova/claude_import/v0/export_manifest_v0.json +32 -0
- package/.tmp_test_zip/out2/mova/claude_import/v0/import_manifest.json +33 -0
- package/.tmp_test_zip/out2/mova/claude_import/v0/input_policy_report_v0.json +38 -0
- package/.tmp_test_zip/out2/mova/claude_import/v0/lint_report_v0.json +6 -0
- package/.tmp_test_zip/out2/mova/claude_import/v0/redaction_report.json +4 -0
- package/.tmp_test_zip/proj/.claude/skills/a.md +1 -0
- package/.tmp_test_zip/proj/.mcp.json +1 -0
- package/.tmp_test_zip/proj/CLAUDE.md +1 -0
- package/README.md +86 -0
- package/create_files.js +52 -0
- package/dist/anthropic_profile_v0.d.ts +2 -0
- package/dist/anthropic_profile_v0.js +66 -0
- package/dist/claude_profile_scaffold_v0.d.ts +2 -0
- package/dist/claude_profile_scaffold_v0.js +110 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +163 -0
- package/dist/cli_entry.d.ts +1 -0
- package/dist/cli_entry.js +1 -0
- package/dist/control_apply_v0.d.ts +6 -0
- package/dist/control_apply_v0.js +86 -0
- package/dist/control_check_v0.d.ts +7 -0
- package/dist/control_check_v0.js +80 -0
- package/dist/control_contracts_v0.d.ts +8 -0
- package/dist/control_contracts_v0.js +17 -0
- package/dist/control_prefill_v0.d.ts +6 -0
- package/dist/control_prefill_v0.js +61 -0
- package/dist/export_zip_v0.d.ts +8 -0
- package/dist/export_zip_v0.js +79 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +2 -0
- package/dist/init_v0.d.ts +7 -0
- package/dist/init_v0.js +47 -0
- package/dist/input_policy_v0.d.ts +26 -0
- package/dist/input_policy_v0.js +76 -0
- package/dist/lint_v0.d.ts +18 -0
- package/dist/lint_v0.js +131 -0
- package/dist/mova_overlay_v0.d.ts +14 -0
- package/dist/mova_overlay_v0.js +65 -0
- package/dist/mova_spec_bindings_v0.d.ts +5 -0
- package/dist/mova_spec_bindings_v0.js +5 -0
- package/dist/quality_v0.d.ts +1 -0
- package/dist/quality_v0.js +223 -0
- package/dist/redaction.d.ts +14 -0
- package/dist/redaction.js +52 -0
- package/dist/run_import.d.ts +2 -0
- package/dist/run_import.js +479 -0
- package/dist/stable_json.d.ts +1 -0
- package/dist/stable_json.js +15 -0
- package/docs/ANTHROPIC_PROFILE_v0.md +38 -0
- package/docs/COMPATIBILITY_MATRIX.md +25 -0
- package/docs/CONTROL_PROFILE_GUIDE_v0.md +40 -0
- package/docs/IMPORT_SPEC_v0.md +30 -0
- package/docs/MOVA_SPEC_BINDINGS.json +21 -0
- package/docs/MOVA_SPEC_BINDINGS.md +11 -0
- package/docs/OPERATOR_GUIDE_v0.md +43 -0
- package/docs/SECURITY_MODEL_v0.md +20 -0
- package/examples/control_profile_min.json +37 -0
- package/examples/control_profile_standard.json +81 -0
- package/examples/control_profile_strict.json +68 -0
- package/fixtures/neg/bad_skill_structure/.claude/skills/bad/README.md +3 -0
- package/fixtures/neg/bad_skill_structure/CLAUDE.md +3 -0
- package/fixtures/neg/local_settings_present/.claude/settings.local.json +3 -0
- package/fixtures/neg/local_settings_present/.claude/skills/alpha/SKILL.md +6 -0
- package/fixtures/neg/local_settings_present/CLAUDE.md +3 -0
- package/fixtures/neg/secret_leak/.claude/skills/alpha/SKILL.md +6 -0
- package/fixtures/neg/secret_leak/.mcp.json +3 -0
- package/fixtures/neg/secret_leak/CLAUDE.md +3 -0
- package/fixtures/neg/strict_denied_local/.claude/settings.local.json +3 -0
- package/fixtures/neg/strict_denied_local/CLAUDE.md +3 -0
- package/fixtures/pos/basic/.claude/skills/alpha/SKILL.md +8 -0
- package/fixtures/pos/basic/.mcp.json +3 -0
- package/fixtures/pos/basic/CLAUDE.md +3 -0
- package/fixtures/pos/control_basic_project/.mcp.json +3 -0
- package/fixtures/pos/control_basic_project/CLAUDE.md +3 -0
- package/fixtures/pos/control_profile_filled/claude_control_profile_v0.json +18 -0
- package/fixtures/pos/full_scaffold_roundtrip/README.md +1 -0
- package/package.json +39 -0
- package/schemas/claude_control/v0/ds/ds.claude_control_mapping_v0.json +227 -0
- package/schemas/claude_control/v0/ds/ds.claude_control_profile_v0.json +114 -0
- package/schemas/claude_control/v0/env/env.claude_control_apply_v0.json +170 -0
- package/schemas/claude_control/v0/env/env.claude_control_import_prefill_v0.json +171 -0
- package/schemas/claude_control/v0/global/global.claude_control_precedence_v0.json +58 -0
- package/schemas/claude_control/v0/global/global.claude_control_vocab_v0.json +98 -0
- package/schemas/ds.claude_import.instruction_profile_v0.schema.json +31 -0
- package/schemas/ds.claude_import.mcp_servers_v0.schema.json +47 -0
- package/schemas/ds.claude_import.skills_catalog_v0.schema.json +48 -0
- package/src/anthropic_profile_v0.ts +68 -0
- package/src/claude_profile_scaffold_v0.ts +117 -0
- package/src/cli.ts +160 -0
- package/src/cli_entry.ts +1 -0
- package/src/control_apply_v0.ts +108 -0
- package/src/control_check_v0.ts +98 -0
- package/src/control_contracts_v0.ts +26 -0
- package/src/control_prefill_v0.ts +74 -0
- package/src/export_zip_v0.ts +90 -0
- package/src/index.ts +29 -0
- package/src/init_v0.ts +59 -0
- package/src/input_policy_v0.ts +103 -0
- package/src/lint_v0.ts +151 -0
- package/src/mova_overlay_v0.ts +79 -0
- package/src/mova_spec_bindings_v0.ts +5 -0
- package/src/quality_v0.ts +264 -0
- package/src/redaction.ts +63 -0
- package/src/run_import.ts +526 -0
- package/src/stable_json.ts +15 -0
- package/test/control_apply_apply.test.js +40 -0
- package/test/control_check_preview.test.js +38 -0
- package/test/control_prefill.test.js +30 -0
- package/test/demo_v0_smoke.test.js +37 -0
- package/test/export_zip_determinism.test.js +41 -0
- package/test/import_determinism.test.js +53 -0
- package/test/init_v0.test.js +37 -0
- package/test/overlay_v0_output.test.js +38 -0
- package/test/profile_v0_output.test.js +44 -0
- package/test/scaffold_v0_output.test.js +64 -0
- package/test/strict_input_policy.test.js +45 -0
- package/tools/demo_v0.mjs +98 -0
- package/tools/deps_audit_v0.mjs +123 -0
- package/tools/write_mova_spec_bindings_v0.mjs +122 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { execFile } from "node:child_process";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { runImport } from "./run_import.js";
|
|
6
|
+
import { stableStringify } from "./stable_json.js";
|
|
7
|
+
import { controlCheckV0 } from "./control_check_v0.js";
|
|
8
|
+
import { controlPrefillV0 } from "./control_prefill_v0.js";
|
|
9
|
+
import { controlApplyV0 } from "./control_apply_v0.js";
|
|
10
|
+
import { writeCleanClaudeProfileScaffoldV0 } from "./claude_profile_scaffold_v0.js";
|
|
11
|
+
|
|
12
|
+
type QualityCaseReport = {
|
|
13
|
+
profile_version: "v0";
|
|
14
|
+
suite: "pos" | "neg";
|
|
15
|
+
case_id: string;
|
|
16
|
+
run_id: string;
|
|
17
|
+
ok: boolean;
|
|
18
|
+
failures: string[];
|
|
19
|
+
checks: {
|
|
20
|
+
input_policy_ok: boolean;
|
|
21
|
+
lint_ok: boolean;
|
|
22
|
+
redaction_hits: number;
|
|
23
|
+
settings_local_input: boolean;
|
|
24
|
+
skill_structure_ok: boolean;
|
|
25
|
+
skill_structure_issues: string[];
|
|
26
|
+
export_manifest_present: boolean;
|
|
27
|
+
zip_present: boolean;
|
|
28
|
+
export_files_count_match: boolean;
|
|
29
|
+
};
|
|
30
|
+
exit_code?: number;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const execFileP = promisify(execFile);
|
|
34
|
+
|
|
35
|
+
function getArg(name: string): string | undefined {
|
|
36
|
+
const idx = process.argv.indexOf(name);
|
|
37
|
+
if (idx === -1) return undefined;
|
|
38
|
+
return process.argv[idx + 1];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function exists(p: string): Promise<boolean> {
|
|
42
|
+
try {
|
|
43
|
+
await fs.stat(p);
|
|
44
|
+
return true;
|
|
45
|
+
} catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function readJson(p: string): Promise<any> {
|
|
51
|
+
const raw = await fs.readFile(p, "utf8");
|
|
52
|
+
return JSON.parse(raw);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function runDepsAudit() {
|
|
56
|
+
try {
|
|
57
|
+
await execFileP("node", ["tools/deps_audit_v0.mjs"], { cwd: process.cwd() });
|
|
58
|
+
} catch (err: any) {
|
|
59
|
+
const code = err?.code ?? 1;
|
|
60
|
+
if (code === 2) {
|
|
61
|
+
throw new Error("deps_audit_failed");
|
|
62
|
+
}
|
|
63
|
+
throw err;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function writeJson(p: string, obj: any) {
|
|
68
|
+
await fs.mkdir(path.dirname(p), { recursive: true });
|
|
69
|
+
await fs.writeFile(p, stableStringify(obj) + "\n", "utf8");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function checkSkillStructure(projectDir: string): Promise<{ ok: boolean; issues: string[] }> {
|
|
73
|
+
const issues: string[] = [];
|
|
74
|
+
const skillsRoot = path.join(projectDir, ".claude", "skills");
|
|
75
|
+
if (!(await exists(skillsRoot))) return { ok: true, issues };
|
|
76
|
+
const entries = await fs.readdir(skillsRoot, { withFileTypes: true });
|
|
77
|
+
for (const entry of entries) {
|
|
78
|
+
const rel = `.claude/skills/${entry.name}`;
|
|
79
|
+
if (entry.isFile()) {
|
|
80
|
+
issues.push(`skill_root_file:${rel}`);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (entry.isDirectory()) {
|
|
84
|
+
const skillMd = path.join(skillsRoot, entry.name, "SKILL.md");
|
|
85
|
+
if (!(await exists(skillMd))) {
|
|
86
|
+
issues.push(`missing_skill_md:${rel}/SKILL.md`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return { ok: issues.length === 0, issues };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function runCase(suite: "pos" | "neg", caseId: string, fixturesRoot: string) {
|
|
94
|
+
const repoRoot = process.cwd();
|
|
95
|
+
const projectDir = path.join(fixturesRoot, caseId);
|
|
96
|
+
const outRoot = path.join(repoRoot, ".tmp_test", "quality", suite, caseId, "out");
|
|
97
|
+
await fs.rm(outRoot, { recursive: true, force: true });
|
|
98
|
+
await fs.mkdir(outRoot, { recursive: true });
|
|
99
|
+
|
|
100
|
+
const strict = suite === "neg" && caseId === "strict_denied_local";
|
|
101
|
+
const result = await runImport({
|
|
102
|
+
projectDir,
|
|
103
|
+
outDir: outRoot,
|
|
104
|
+
includeLocal: false,
|
|
105
|
+
includeUserSettings: false,
|
|
106
|
+
dryRun: false,
|
|
107
|
+
strict,
|
|
108
|
+
emitProfile: true,
|
|
109
|
+
emitOverlay: true,
|
|
110
|
+
emitZip: true,
|
|
111
|
+
zipName: "export.zip",
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const movaBase = path.join(outRoot, "mova", "claude_import", "v0");
|
|
115
|
+
const manifestPath = path.join(movaBase, "import_manifest.json");
|
|
116
|
+
const lintPath = path.join(movaBase, "lint_report_v0.json");
|
|
117
|
+
const redactionPath = path.join(movaBase, "redaction_report.json");
|
|
118
|
+
const exportManifestPath = path.join(movaBase, "export_manifest_v0.json");
|
|
119
|
+
const inputPolicyPath = path.join(movaBase, "input_policy_report_v0.json");
|
|
120
|
+
|
|
121
|
+
const failures: string[] = [];
|
|
122
|
+
|
|
123
|
+
const inputPolicyReport = await readJson(inputPolicyPath);
|
|
124
|
+
const lintReport = await exists(lintPath) ? await readJson(lintPath) : null;
|
|
125
|
+
const redactionReport = await exists(redactionPath) ? await readJson(redactionPath) : { hits: [] };
|
|
126
|
+
const exportManifestExists = await exists(exportManifestPath);
|
|
127
|
+
const exportManifest = exportManifestExists ? await readJson(exportManifestPath) : null;
|
|
128
|
+
|
|
129
|
+
const settingsLocalInput = await exists(path.join(projectDir, ".claude", "settings.local.json"));
|
|
130
|
+
const skillStructure = await checkSkillStructure(projectDir);
|
|
131
|
+
|
|
132
|
+
const zipRelPath = exportManifest?.zip_rel_path;
|
|
133
|
+
const zipPresent = typeof zipRelPath === "string" && (await exists(path.join(outRoot, zipRelPath)));
|
|
134
|
+
const exportFilesCountMatch =
|
|
135
|
+
typeof exportManifest?.files_count === "number" &&
|
|
136
|
+
Array.isArray(exportManifest?.files) &&
|
|
137
|
+
exportManifest.files_count === exportManifest.files.length;
|
|
138
|
+
|
|
139
|
+
if (!(await exists(manifestPath))) failures.push("missing_import_manifest");
|
|
140
|
+
if (!inputPolicyReport?.ok && suite === "pos") failures.push("input_policy_not_ok");
|
|
141
|
+
if (!lintReport?.ok && !strict) failures.push("lint_not_ok");
|
|
142
|
+
if (Array.isArray(redactionReport?.hits) && redactionReport.hits.length > 0) failures.push("redaction_hits_present");
|
|
143
|
+
if (settingsLocalInput) failures.push("settings_local_input_present");
|
|
144
|
+
if (!skillStructure.ok) failures.push("skill_structure_invalid");
|
|
145
|
+
if (!strict) {
|
|
146
|
+
if (!exportManifestExists) failures.push("missing_export_manifest");
|
|
147
|
+
if (!zipPresent) failures.push("zip_missing");
|
|
148
|
+
if (!exportFilesCountMatch) failures.push("export_files_count_mismatch");
|
|
149
|
+
} else {
|
|
150
|
+
if (result.exit_code !== 2) failures.push("strict_exit_code_not_2");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const ok = failures.length === 0;
|
|
154
|
+
const report: QualityCaseReport = {
|
|
155
|
+
profile_version: "v0",
|
|
156
|
+
suite,
|
|
157
|
+
case_id: caseId,
|
|
158
|
+
run_id: result.run_id,
|
|
159
|
+
ok,
|
|
160
|
+
failures,
|
|
161
|
+
checks: {
|
|
162
|
+
input_policy_ok: Boolean(inputPolicyReport?.ok),
|
|
163
|
+
lint_ok: Boolean(lintReport?.ok),
|
|
164
|
+
redaction_hits: Array.isArray(redactionReport?.hits) ? redactionReport.hits.length : 0,
|
|
165
|
+
settings_local_input: settingsLocalInput,
|
|
166
|
+
skill_structure_ok: skillStructure.ok,
|
|
167
|
+
skill_structure_issues: skillStructure.issues,
|
|
168
|
+
export_manifest_present: exportManifestExists,
|
|
169
|
+
zip_present: zipPresent,
|
|
170
|
+
export_files_count_match: exportFilesCountMatch,
|
|
171
|
+
},
|
|
172
|
+
exit_code: result.exit_code,
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const reportPath = path.join(repoRoot, "artifacts", "quality_v0", result.run_id, "quality_report_v0.json");
|
|
176
|
+
await writeJson(reportPath, report);
|
|
177
|
+
|
|
178
|
+
return report;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function main() {
|
|
182
|
+
const suiteArg = getArg("--suite") ?? "pos";
|
|
183
|
+
if (suiteArg !== "pos" && suiteArg !== "neg") {
|
|
184
|
+
console.error(`Unknown suite: ${suiteArg}`);
|
|
185
|
+
process.exit(2);
|
|
186
|
+
}
|
|
187
|
+
const suite = suiteArg as "pos" | "neg";
|
|
188
|
+
if (suite === "pos") {
|
|
189
|
+
await runDepsAudit();
|
|
190
|
+
}
|
|
191
|
+
const fixturesRoot = path.join(process.cwd(), "fixtures", suite);
|
|
192
|
+
const entries = await fs.readdir(fixturesRoot, { withFileTypes: true });
|
|
193
|
+
const cases = entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
194
|
+
if (!cases.length) {
|
|
195
|
+
console.error(`No fixtures found in ${fixturesRoot}`);
|
|
196
|
+
process.exit(2);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const reports: QualityCaseReport[] = [];
|
|
200
|
+
for (const caseId of cases) {
|
|
201
|
+
reports.push(await runCase(suite, caseId, fixturesRoot));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (suite === "pos") {
|
|
205
|
+
const projectDir = path.join(fixturesRoot, "control_basic_project");
|
|
206
|
+
const profilePath = path.join(
|
|
207
|
+
process.cwd(),
|
|
208
|
+
"fixtures",
|
|
209
|
+
"pos",
|
|
210
|
+
"control_profile_filled",
|
|
211
|
+
"claude_control_profile_v0.json"
|
|
212
|
+
);
|
|
213
|
+
const outDir = path.join(process.cwd(), ".tmp_test", "quality", "control_check");
|
|
214
|
+
await controlCheckV0(projectDir, profilePath, outDir);
|
|
215
|
+
|
|
216
|
+
const roundtripDir = path.join(process.cwd(), ".tmp_test", "quality", "full_scaffold_roundtrip");
|
|
217
|
+
const profileOut = path.join(roundtripDir, "profile");
|
|
218
|
+
const runOut = path.join(roundtripDir, "out");
|
|
219
|
+
await fs.rm(roundtripDir, { recursive: true, force: true });
|
|
220
|
+
await fs.mkdir(roundtripDir, { recursive: true });
|
|
221
|
+
await writeCleanClaudeProfileScaffoldV0(roundtripDir);
|
|
222
|
+
const prefill = await controlPrefillV0(roundtripDir, profileOut);
|
|
223
|
+
await controlCheckV0(roundtripDir, prefill.profile_path, runOut);
|
|
224
|
+
await controlApplyV0(roundtripDir, prefill.profile_path, runOut, "apply");
|
|
225
|
+
await runImport({
|
|
226
|
+
projectDir: roundtripDir,
|
|
227
|
+
outDir: path.join(roundtripDir, "rebuild"),
|
|
228
|
+
includeLocal: false,
|
|
229
|
+
includeUserSettings: false,
|
|
230
|
+
dryRun: false,
|
|
231
|
+
strict: false,
|
|
232
|
+
emitProfile: true,
|
|
233
|
+
emitOverlay: true,
|
|
234
|
+
emitZip: true,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const failed = reports.filter((r) => !r.ok);
|
|
239
|
+
const passed = reports.filter((r) => r.ok);
|
|
240
|
+
|
|
241
|
+
let ok = true;
|
|
242
|
+
if (suite === "pos") {
|
|
243
|
+
ok = failed.length === 0;
|
|
244
|
+
} else {
|
|
245
|
+
ok = passed.length === 0;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
console.log(
|
|
249
|
+
[
|
|
250
|
+
`quality_v0 suite=${suite}`,
|
|
251
|
+
`cases=${reports.length}`,
|
|
252
|
+
`passed=${passed.length}`,
|
|
253
|
+
`failed=${failed.length}`,
|
|
254
|
+
`ok=${ok}`,
|
|
255
|
+
].join(" ")
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
process.exit(ok ? 0 : 1);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
main().catch((err) => {
|
|
262
|
+
console.error(err);
|
|
263
|
+
process.exit(1);
|
|
264
|
+
});
|
package/src/redaction.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export type RedactionHit = {
|
|
4
|
+
rule_id: string;
|
|
5
|
+
key?: string;
|
|
6
|
+
len?: number;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const KEY_RE = /(api[_-]?key|token|secret|password|authorization|bearer)/i;
|
|
10
|
+
const INLINE_SECRET_RE = /(sk-[a-zA-Z0-9]{8,})/g; // best‑effort
|
|
11
|
+
|
|
12
|
+
export function redactText(input: string): { redacted: string; hits: RedactionHit[] } {
|
|
13
|
+
const hits: RedactionHit[] = [];
|
|
14
|
+
let out = input;
|
|
15
|
+
|
|
16
|
+
// redact obvious inline tokens
|
|
17
|
+
out = out.replace(INLINE_SECRET_RE, (m) => {
|
|
18
|
+
hits.push({ rule_id: "inline_token_like", len: m.length });
|
|
19
|
+
return "[REDACTED_TOKEN]";
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// redact KEY=VALUE lines (best‑effort)
|
|
23
|
+
out = out.replace(/^([A-Z0-9_]{3,80})\s*=\s*(.+)$/gmi, (line, k, v) => {
|
|
24
|
+
if (!KEY_RE.test(k)) return line;
|
|
25
|
+
hits.push({ rule_id: "key_value_line", key: k, len: String(v).length });
|
|
26
|
+
return `${k}=[REDACTED_VALUE_LEN_${String(v).length}]`;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return { redacted: out, hits };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function redactJson(obj: unknown): { redacted: unknown; hits: RedactionHit[] } {
|
|
33
|
+
const hits: RedactionHit[] = [];
|
|
34
|
+
|
|
35
|
+
function walk(x: any): any {
|
|
36
|
+
if (x === null || x === undefined) return x;
|
|
37
|
+
if (typeof x === "string") {
|
|
38
|
+
const r = redactText(x);
|
|
39
|
+
hits.push(...r.hits);
|
|
40
|
+
return r.redacted;
|
|
41
|
+
}
|
|
42
|
+
if (Array.isArray(x)) return x.map(walk);
|
|
43
|
+
if (typeof x === "object") {
|
|
44
|
+
const out: any = {};
|
|
45
|
+
for (const [k, v] of Object.entries(x)) {
|
|
46
|
+
if (KEY_RE.test(k) && typeof v === "string") {
|
|
47
|
+
hits.push({ rule_id: "json_secret_field", key: k, len: v.length });
|
|
48
|
+
out[k] = `[REDACTED_VALUE_LEN_${v.length}]`;
|
|
49
|
+
} else {
|
|
50
|
+
out[k] = walk(v);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
return x;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { redacted: walk(obj), hits };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function stableSha256(text: string): string {
|
|
62
|
+
return crypto.createHash("sha256").update(text, "utf8").digest("hex");
|
|
63
|
+
}
|