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,86 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { stableStringify } from "./stable_json.js";
|
|
4
|
+
import { stableSha256 } from "./redaction.js";
|
|
5
|
+
import { buildMovaControlEntryV0, MOVA_CONTROL_ENTRY_MARKER } from "./mova_overlay_v0.js";
|
|
6
|
+
import { ensureClaudeControlSurfacesV0 } from "./claude_profile_scaffold_v0.js";
|
|
7
|
+
async function exists(p) {
|
|
8
|
+
try {
|
|
9
|
+
await fs.stat(p);
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async function readJson(p) {
|
|
17
|
+
const raw = await fs.readFile(p, "utf8");
|
|
18
|
+
return JSON.parse(raw);
|
|
19
|
+
}
|
|
20
|
+
async function writeJson(p, obj) {
|
|
21
|
+
await fs.mkdir(path.dirname(p), { recursive: true });
|
|
22
|
+
await fs.writeFile(p, stableStringify(obj) + "\n", "utf8");
|
|
23
|
+
}
|
|
24
|
+
function computeRunId(parts) {
|
|
25
|
+
return stableSha256(parts.join("|")).slice(0, 16);
|
|
26
|
+
}
|
|
27
|
+
function updateClaude(content, marker, block) {
|
|
28
|
+
if (content.includes(marker)) {
|
|
29
|
+
const idx = content.indexOf(marker);
|
|
30
|
+
const after = content.slice(idx);
|
|
31
|
+
const split = after.split("\n\n");
|
|
32
|
+
split[0] = block.trimEnd();
|
|
33
|
+
return content.slice(0, idx) + split.join("\n\n");
|
|
34
|
+
}
|
|
35
|
+
return `${block}\n${content}`;
|
|
36
|
+
}
|
|
37
|
+
export async function controlApplyV0(projectDir, profilePath, outDir, mode) {
|
|
38
|
+
await ensureClaudeControlSurfacesV0(projectDir);
|
|
39
|
+
const profile = await readJson(profilePath);
|
|
40
|
+
const applyMode = mode ?? profile?.apply?.default_apply_mode ?? "preview";
|
|
41
|
+
const claudePath = path.join(projectDir, "CLAUDE.md");
|
|
42
|
+
const mcpPath = path.join(projectDir, ".mcp.json");
|
|
43
|
+
const claudeExists = await exists(claudePath);
|
|
44
|
+
const mcpExists = await exists(mcpPath);
|
|
45
|
+
const marker = profile?.anthropic?.claude_md?.marker ?? MOVA_CONTROL_ENTRY_MARKER;
|
|
46
|
+
const runId = computeRunId([profilePath, claudeExists ? "claude" : "", mcpExists ? "mcp" : "", marker, applyMode]);
|
|
47
|
+
const runBase = path.join(outDir, "mova", "claude_control", "v0", "runs", runId);
|
|
48
|
+
const overlayParams = {
|
|
49
|
+
contractsDir: "mova/claude_import/v0/contracts/",
|
|
50
|
+
artifactsDir: "mova/claude_import/v0/",
|
|
51
|
+
instructionProfileFile: "instruction_profile_v0.json",
|
|
52
|
+
skillsCatalogFile: "skills_catalog_v0.json",
|
|
53
|
+
mcpServersFile: "mcp_servers_v0.json",
|
|
54
|
+
lintReportFile: "lint_report_v0.json",
|
|
55
|
+
qualityReportFile: "quality_report_v0.json",
|
|
56
|
+
exportManifestFile: "export_manifest_v0.json",
|
|
57
|
+
};
|
|
58
|
+
const controlEntry = buildMovaControlEntryV0(overlayParams);
|
|
59
|
+
const applied = { claude_md: false, mcp_json: false, settings: false };
|
|
60
|
+
if (applyMode === "apply") {
|
|
61
|
+
if (profile?.anthropic?.claude_md?.inject_control_entry && claudeExists) {
|
|
62
|
+
const raw = await fs.readFile(claudePath, "utf8");
|
|
63
|
+
const updated = updateClaude(raw, marker, controlEntry);
|
|
64
|
+
await fs.writeFile(claudePath, updated, "utf8");
|
|
65
|
+
applied.claude_md = true;
|
|
66
|
+
}
|
|
67
|
+
if (profile?.anthropic?.mcp?.servers && mcpExists) {
|
|
68
|
+
const mcp = await readJson(mcpPath);
|
|
69
|
+
const merged = { ...mcp, servers: profile.anthropic.mcp.servers };
|
|
70
|
+
await fs.writeFile(mcpPath, stableStringify(merged) + "\n", "utf8");
|
|
71
|
+
applied.mcp_json = true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const report = {
|
|
75
|
+
profile_version: "v0",
|
|
76
|
+
run_id: runId,
|
|
77
|
+
project_dir: projectDir,
|
|
78
|
+
profile_path: profilePath,
|
|
79
|
+
mode: applyMode,
|
|
80
|
+
outcome_code: applyMode === "apply" ? "APPLIED" : "PREVIEW",
|
|
81
|
+
applied,
|
|
82
|
+
};
|
|
83
|
+
const reportPath = path.join(runBase, "control_apply_report_v0.json");
|
|
84
|
+
await writeJson(reportPath, report);
|
|
85
|
+
return { run_id: runId, report_path: reportPath };
|
|
86
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { stableStringify } from "./stable_json.js";
|
|
4
|
+
import { stableSha256 } from "./redaction.js";
|
|
5
|
+
import { buildMovaControlEntryV0, MOVA_CONTROL_ENTRY_MARKER } from "./mova_overlay_v0.js";
|
|
6
|
+
async function exists(p) {
|
|
7
|
+
try {
|
|
8
|
+
await fs.stat(p);
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async function readJson(p) {
|
|
16
|
+
const raw = await fs.readFile(p, "utf8");
|
|
17
|
+
return JSON.parse(raw);
|
|
18
|
+
}
|
|
19
|
+
async function writeJson(p, obj) {
|
|
20
|
+
await fs.mkdir(path.dirname(p), { recursive: true });
|
|
21
|
+
await fs.writeFile(p, stableStringify(obj) + "\n", "utf8");
|
|
22
|
+
}
|
|
23
|
+
function computeRunId(parts) {
|
|
24
|
+
return stableSha256(parts.join("|")).slice(0, 16);
|
|
25
|
+
}
|
|
26
|
+
export async function controlCheckV0(projectDir, profilePath, outDir) {
|
|
27
|
+
const profile = await readJson(profilePath);
|
|
28
|
+
const claudePath = path.join(projectDir, "CLAUDE.md");
|
|
29
|
+
const mcpPath = path.join(projectDir, ".mcp.json");
|
|
30
|
+
const claudeExists = await exists(claudePath);
|
|
31
|
+
const mcpExists = await exists(mcpPath);
|
|
32
|
+
const marker = profile?.anthropic?.claude_md?.marker ?? MOVA_CONTROL_ENTRY_MARKER;
|
|
33
|
+
const runId = computeRunId([profilePath, claudeExists ? "claude" : "", mcpExists ? "mcp" : "", marker]);
|
|
34
|
+
const runBase = path.join(outDir, "mova", "claude_control", "v0", "runs", runId);
|
|
35
|
+
const overlayParams = {
|
|
36
|
+
contractsDir: "mova/claude_import/v0/contracts/",
|
|
37
|
+
artifactsDir: "mova/claude_import/v0/",
|
|
38
|
+
instructionProfileFile: "instruction_profile_v0.json",
|
|
39
|
+
skillsCatalogFile: "skills_catalog_v0.json",
|
|
40
|
+
mcpServersFile: "mcp_servers_v0.json",
|
|
41
|
+
lintReportFile: "lint_report_v0.json",
|
|
42
|
+
qualityReportFile: "quality_report_v0.json",
|
|
43
|
+
exportManifestFile: "export_manifest_v0.json",
|
|
44
|
+
};
|
|
45
|
+
const actions = [];
|
|
46
|
+
if (profile?.anthropic?.claude_md?.inject_control_entry) {
|
|
47
|
+
actions.push({
|
|
48
|
+
target: "CLAUDE.md",
|
|
49
|
+
action: "insert_or_update_control_entry",
|
|
50
|
+
marker,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
if (profile?.anthropic?.mcp?.servers && mcpExists) {
|
|
54
|
+
actions.push({
|
|
55
|
+
target: ".mcp.json",
|
|
56
|
+
action: "merge_servers",
|
|
57
|
+
summary: "merge profile servers with project mcp.json",
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
const plan = {
|
|
61
|
+
profile_version: "v0",
|
|
62
|
+
run_id: runId,
|
|
63
|
+
project_dir: projectDir,
|
|
64
|
+
profile_path: profilePath,
|
|
65
|
+
actions,
|
|
66
|
+
};
|
|
67
|
+
const summary = {
|
|
68
|
+
run_id: runId,
|
|
69
|
+
outcome_code: "PREVIEW",
|
|
70
|
+
actions_count: actions.length,
|
|
71
|
+
control_entry_preview: profile?.anthropic?.claude_md?.inject_control_entry
|
|
72
|
+
? buildMovaControlEntryV0(overlayParams)
|
|
73
|
+
: null,
|
|
74
|
+
};
|
|
75
|
+
const planPath = path.join(runBase, "control_plan_v0.json");
|
|
76
|
+
const summaryPath = path.join(runBase, "control_summary_v0.json");
|
|
77
|
+
await writeJson(planPath, plan);
|
|
78
|
+
await writeJson(summaryPath, summary);
|
|
79
|
+
return { run_id: runId, plan_path: planPath, summary_path: summaryPath };
|
|
80
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
async function loadJson(relPath) {
|
|
4
|
+
const url = new URL(`../${relPath}`, import.meta.url);
|
|
5
|
+
const abs = fileURLToPath(url);
|
|
6
|
+
const raw = await fs.readFile(abs, "utf8");
|
|
7
|
+
return JSON.parse(raw);
|
|
8
|
+
}
|
|
9
|
+
export async function loadControlContractsV0() {
|
|
10
|
+
const base = "schemas/claude_control/v0";
|
|
11
|
+
return {
|
|
12
|
+
claude_control_profile: await loadJson(`${base}/ds/ds.claude_control_profile_v0.json`),
|
|
13
|
+
claude_control_mapping: await loadJson(`${base}/ds/ds.claude_control_mapping_v0.json`),
|
|
14
|
+
claude_control_vocab: await loadJson(`${base}/global/global.claude_control_vocab_v0.json`),
|
|
15
|
+
claude_control_precedence: await loadJson(`${base}/global/global.claude_control_precedence_v0.json`),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { stableStringify } from "./stable_json.js";
|
|
4
|
+
import { loadControlContractsV0 } from "./control_contracts_v0.js";
|
|
5
|
+
async function exists(p) {
|
|
6
|
+
try {
|
|
7
|
+
await fs.stat(p);
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
async function readJson(p) {
|
|
15
|
+
const raw = await fs.readFile(p, "utf8");
|
|
16
|
+
return JSON.parse(raw);
|
|
17
|
+
}
|
|
18
|
+
async function writeJson(p, obj) {
|
|
19
|
+
await fs.mkdir(path.dirname(p), { recursive: true });
|
|
20
|
+
await fs.writeFile(p, stableStringify(obj) + "\n", "utf8");
|
|
21
|
+
}
|
|
22
|
+
export async function controlPrefillV0(projectDir, outDir) {
|
|
23
|
+
const contracts = await loadControlContractsV0();
|
|
24
|
+
const template = JSON.parse(JSON.stringify(contracts.claude_control_profile));
|
|
25
|
+
const mcpPath = path.join(projectDir, ".mcp.json");
|
|
26
|
+
const settingsPath = path.join(projectDir, ".claude", "settings.json");
|
|
27
|
+
const settingsLocalPath = path.join(projectDir, ".claude", "settings.local.json");
|
|
28
|
+
let mcpServers = {};
|
|
29
|
+
let mcpFound = false;
|
|
30
|
+
if (await exists(mcpPath)) {
|
|
31
|
+
const parsed = await readJson(mcpPath);
|
|
32
|
+
if (Array.isArray(parsed?.servers)) {
|
|
33
|
+
mcpServers = { servers: parsed.servers };
|
|
34
|
+
}
|
|
35
|
+
else if (parsed?.servers && typeof parsed.servers === "object") {
|
|
36
|
+
mcpServers = { servers: parsed.servers };
|
|
37
|
+
}
|
|
38
|
+
mcpFound = true;
|
|
39
|
+
}
|
|
40
|
+
if (template?.anthropic?.mcp) {
|
|
41
|
+
template.anthropic.mcp.servers = mcpServers.servers ?? {};
|
|
42
|
+
}
|
|
43
|
+
const profilePath = path.join(outDir, "claude_control_profile_v0.json");
|
|
44
|
+
const reportPath = path.join(outDir, "prefill_report_v0.json");
|
|
45
|
+
const report = {
|
|
46
|
+
profile_version: "v0",
|
|
47
|
+
project_dir: projectDir,
|
|
48
|
+
profile_path: profilePath,
|
|
49
|
+
found: {
|
|
50
|
+
mcp_json: mcpFound,
|
|
51
|
+
settings_json: await exists(settingsPath),
|
|
52
|
+
settings_local_json: await exists(settingsLocalPath),
|
|
53
|
+
},
|
|
54
|
+
applied: {
|
|
55
|
+
mcp_servers: mcpFound,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
await writeJson(profilePath, template);
|
|
59
|
+
await writeJson(reportPath, report);
|
|
60
|
+
return { profile_path: profilePath, report_path: reportPath };
|
|
61
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import fsp from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import crypto from "node:crypto";
|
|
5
|
+
import yazl from "yazl";
|
|
6
|
+
const FIXED_MTIME = new Date(Date.UTC(2000, 0, 1, 0, 0, 0));
|
|
7
|
+
function shouldExclude(rel) {
|
|
8
|
+
const parts = rel.split("/");
|
|
9
|
+
for (const part of parts) {
|
|
10
|
+
if (part === "node_modules" || part === "dist" || part === "artifacts")
|
|
11
|
+
return true;
|
|
12
|
+
if (part.startsWith(".tmp"))
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
async function listFilesRec(dir) {
|
|
18
|
+
const out = [];
|
|
19
|
+
const stack = [dir];
|
|
20
|
+
while (stack.length) {
|
|
21
|
+
const current = stack.pop();
|
|
22
|
+
const entries = await fsp.readdir(current, { withFileTypes: true });
|
|
23
|
+
for (const e of entries) {
|
|
24
|
+
const abs = path.join(current, e.name);
|
|
25
|
+
if (e.isDirectory())
|
|
26
|
+
stack.push(abs);
|
|
27
|
+
else if (e.isFile())
|
|
28
|
+
out.push(abs);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
function normalizeRelPath(root, abs) {
|
|
34
|
+
return path.relative(root, abs).replace(/\\/g, "/");
|
|
35
|
+
}
|
|
36
|
+
async function sha256File(p) {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
const hash = crypto.createHash("sha256");
|
|
39
|
+
const stream = fs.createReadStream(p);
|
|
40
|
+
stream.on("error", reject);
|
|
41
|
+
stream.on("data", (d) => hash.update(d));
|
|
42
|
+
stream.on("end", () => resolve(hash.digest("hex")));
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
async function writeZip(absPath, files) {
|
|
46
|
+
await fsp.mkdir(path.dirname(absPath), { recursive: true });
|
|
47
|
+
const zipfile = new yazl.ZipFile();
|
|
48
|
+
for (const f of files) {
|
|
49
|
+
zipfile.addFile(f.abs, f.rel, { mtime: FIXED_MTIME, mode: 0o100644 });
|
|
50
|
+
}
|
|
51
|
+
const outStream = fs.createWriteStream(absPath);
|
|
52
|
+
const done = new Promise((resolve, reject) => {
|
|
53
|
+
outStream.on("close", resolve);
|
|
54
|
+
outStream.on("error", reject);
|
|
55
|
+
});
|
|
56
|
+
zipfile.outputStream.pipe(outStream);
|
|
57
|
+
zipfile.end();
|
|
58
|
+
await done;
|
|
59
|
+
}
|
|
60
|
+
export async function createExportZipV0(outRoot, zipName) {
|
|
61
|
+
const name = zipName && zipName.trim().length ? zipName.trim() : "export.zip";
|
|
62
|
+
const zipAbsPath = path.isAbsolute(name) ? name : path.join(outRoot, name);
|
|
63
|
+
const zipRelPath = normalizeRelPath(outRoot, zipAbsPath);
|
|
64
|
+
const absFiles = await listFilesRec(outRoot);
|
|
65
|
+
const files = absFiles
|
|
66
|
+
.map((abs) => ({ abs, rel: normalizeRelPath(outRoot, abs) }))
|
|
67
|
+
.filter((f) => !shouldExclude(f.rel))
|
|
68
|
+
.filter((f) => f.rel !== zipRelPath)
|
|
69
|
+
.filter((f) => f.rel !== "mova/claude_import/v0/export_manifest_v0.json")
|
|
70
|
+
.sort((a, b) => a.rel.localeCompare(b.rel));
|
|
71
|
+
await writeZip(zipAbsPath, files);
|
|
72
|
+
const zipSha256 = await sha256File(zipAbsPath);
|
|
73
|
+
return {
|
|
74
|
+
zipAbsPath,
|
|
75
|
+
zipRelPath,
|
|
76
|
+
zipSha256,
|
|
77
|
+
files: files.map((f) => f.rel),
|
|
78
|
+
};
|
|
79
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type ImportOptions = {
|
|
2
|
+
projectDir: string;
|
|
3
|
+
outDir: string;
|
|
4
|
+
includeLocal: boolean;
|
|
5
|
+
includeUserSettings: boolean;
|
|
6
|
+
dryRun: boolean;
|
|
7
|
+
strict: boolean;
|
|
8
|
+
emitProfile: boolean;
|
|
9
|
+
emitZip: boolean;
|
|
10
|
+
zipName?: string;
|
|
11
|
+
emitOverlay: boolean;
|
|
12
|
+
};
|
|
13
|
+
export type ImportResult = {
|
|
14
|
+
ok: boolean;
|
|
15
|
+
exit_code?: number;
|
|
16
|
+
run_id: string;
|
|
17
|
+
out_dir: string;
|
|
18
|
+
imported: {
|
|
19
|
+
claude_md: boolean;
|
|
20
|
+
mcp_json: boolean;
|
|
21
|
+
skills_count: number;
|
|
22
|
+
};
|
|
23
|
+
skipped: Array<{
|
|
24
|
+
path: string;
|
|
25
|
+
reason: string;
|
|
26
|
+
}>;
|
|
27
|
+
lint_summary: string;
|
|
28
|
+
};
|
|
29
|
+
export { runImport } from "./run_import.js";
|
|
30
|
+
export { scanInputPolicyV0 } from "./input_policy_v0.js";
|
package/dist/index.js
ADDED
package/dist/init_v0.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { getAnthropicProfileV0Files } from "./anthropic_profile_v0.js";
|
|
4
|
+
import { stableStringify } from "./stable_json.js";
|
|
5
|
+
import { createExportZipV0 } from "./export_zip_v0.js";
|
|
6
|
+
import { writeCleanClaudeProfileScaffoldV0 } from "./claude_profile_scaffold_v0.js";
|
|
7
|
+
async function writeTextFile(absPath, content) {
|
|
8
|
+
await fs.mkdir(path.dirname(absPath), { recursive: true });
|
|
9
|
+
await fs.writeFile(absPath, content, "utf8");
|
|
10
|
+
}
|
|
11
|
+
async function writeJsonFile(absPath, obj) {
|
|
12
|
+
await fs.mkdir(path.dirname(absPath), { recursive: true });
|
|
13
|
+
await fs.writeFile(absPath, stableStringify(obj) + "\n", "utf8");
|
|
14
|
+
}
|
|
15
|
+
export async function initProfileV0(outRoot, emitZip) {
|
|
16
|
+
const createdFiles = [];
|
|
17
|
+
await writeCleanClaudeProfileScaffoldV0(outRoot);
|
|
18
|
+
const profileFiles = getAnthropicProfileV0Files();
|
|
19
|
+
for (const [rel, content] of Object.entries(profileFiles)) {
|
|
20
|
+
if (rel === "CLAUDE.md" || rel === ".claude/settings.json")
|
|
21
|
+
continue;
|
|
22
|
+
await writeTextFile(path.join(outRoot, rel), content);
|
|
23
|
+
createdFiles.push(rel);
|
|
24
|
+
}
|
|
25
|
+
const movaBase = path.join(outRoot, "mova", "claude_import", "v0");
|
|
26
|
+
const initManifestRel = path.join("mova", "claude_import", "v0", "init_manifest_v0.json").replace(/\\/g, "/");
|
|
27
|
+
let zipRelPath;
|
|
28
|
+
let zipSha256;
|
|
29
|
+
if (emitZip) {
|
|
30
|
+
const exportZip = await createExportZipV0(outRoot);
|
|
31
|
+
zipRelPath = exportZip.zipRelPath;
|
|
32
|
+
zipSha256 = exportZip.zipSha256;
|
|
33
|
+
}
|
|
34
|
+
const initManifest = {
|
|
35
|
+
profile_version: "v0",
|
|
36
|
+
created_files: createdFiles.slice().sort(),
|
|
37
|
+
zip_rel_path: zipRelPath ?? null,
|
|
38
|
+
zip_sha256: zipSha256 ?? null,
|
|
39
|
+
};
|
|
40
|
+
await writeJsonFile(path.join(outRoot, initManifestRel), initManifest);
|
|
41
|
+
createdFiles.push(initManifestRel);
|
|
42
|
+
return {
|
|
43
|
+
createdFiles,
|
|
44
|
+
zipRelPath,
|
|
45
|
+
zipSha256,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type InputPolicyKind = "claude_settings_local" | "local_file" | "env_file" | "private_key_like" | "other";
|
|
2
|
+
export type InputPolicyEntry = {
|
|
3
|
+
path: string;
|
|
4
|
+
kind: InputPolicyKind;
|
|
5
|
+
};
|
|
6
|
+
export type InputPolicyDenied = InputPolicyEntry & {
|
|
7
|
+
reason: string;
|
|
8
|
+
};
|
|
9
|
+
export type InputPolicyReportV0 = {
|
|
10
|
+
policy_version: "v0";
|
|
11
|
+
found: InputPolicyEntry[];
|
|
12
|
+
denied: InputPolicyDenied[];
|
|
13
|
+
allowed: InputPolicyEntry[];
|
|
14
|
+
opts: {
|
|
15
|
+
strict: boolean;
|
|
16
|
+
include_local: boolean;
|
|
17
|
+
};
|
|
18
|
+
ok: boolean;
|
|
19
|
+
exit_code_recommended: 0 | 2;
|
|
20
|
+
};
|
|
21
|
+
type ScanOpts = {
|
|
22
|
+
strict: boolean;
|
|
23
|
+
include_local: boolean;
|
|
24
|
+
};
|
|
25
|
+
export declare function scanInputPolicyV0(projectRoot: string, opts: ScanOpts): Promise<InputPolicyReportV0>;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
async function listFilesRec(dir) {
|
|
4
|
+
const out = [];
|
|
5
|
+
const stack = [dir];
|
|
6
|
+
while (stack.length) {
|
|
7
|
+
const current = stack.pop();
|
|
8
|
+
const entries = await fs.readdir(current, { withFileTypes: true });
|
|
9
|
+
for (const e of entries) {
|
|
10
|
+
const abs = path.join(current, e.name);
|
|
11
|
+
if (e.isDirectory())
|
|
12
|
+
stack.push(abs);
|
|
13
|
+
else if (e.isFile())
|
|
14
|
+
out.push(abs);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
19
|
+
function classify(relPath) {
|
|
20
|
+
const rel = relPath.replace(/\\/g, "/");
|
|
21
|
+
const base = path.posix.basename(rel);
|
|
22
|
+
if (rel.endsWith("/.claude/settings.local.json") || base === "settings.local.json") {
|
|
23
|
+
return "claude_settings_local";
|
|
24
|
+
}
|
|
25
|
+
if (base === "CLAUDE.local.md" || base.includes(".local.")) {
|
|
26
|
+
return "local_file";
|
|
27
|
+
}
|
|
28
|
+
if (base === ".env" || base.startsWith(".env.")) {
|
|
29
|
+
return "env_file";
|
|
30
|
+
}
|
|
31
|
+
if (base.startsWith("id_rsa") || base.endsWith(".pem") || base.endsWith(".key")) {
|
|
32
|
+
return "private_key_like";
|
|
33
|
+
}
|
|
34
|
+
return "other";
|
|
35
|
+
}
|
|
36
|
+
export async function scanInputPolicyV0(projectRoot, opts) {
|
|
37
|
+
const absFiles = await listFilesRec(projectRoot);
|
|
38
|
+
const found = [];
|
|
39
|
+
const denied = [];
|
|
40
|
+
const allowed = [];
|
|
41
|
+
for (const abs of absFiles) {
|
|
42
|
+
const rel = path.relative(projectRoot, abs).replace(/\\/g, "/");
|
|
43
|
+
const kind = classify(rel);
|
|
44
|
+
const entry = { path: rel, kind };
|
|
45
|
+
found.push(entry);
|
|
46
|
+
if (kind === "env_file") {
|
|
47
|
+
denied.push({ ...entry, reason: "env_files_not_allowed" });
|
|
48
|
+
}
|
|
49
|
+
else if (kind === "private_key_like") {
|
|
50
|
+
denied.push({ ...entry, reason: "private_keys_not_allowed" });
|
|
51
|
+
}
|
|
52
|
+
else if (kind === "claude_settings_local") {
|
|
53
|
+
denied.push({ ...entry, reason: "settings_local_not_allowed" });
|
|
54
|
+
}
|
|
55
|
+
else if (kind === "local_file") {
|
|
56
|
+
if (opts.include_local)
|
|
57
|
+
allowed.push(entry);
|
|
58
|
+
else
|
|
59
|
+
denied.push({ ...entry, reason: "local_files_not_allowed" });
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
allowed.push(entry);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const ok = denied.length === 0;
|
|
66
|
+
const exit_code_recommended = !ok && opts.strict ? 2 : 0;
|
|
67
|
+
return {
|
|
68
|
+
policy_version: "v0",
|
|
69
|
+
found,
|
|
70
|
+
denied,
|
|
71
|
+
allowed,
|
|
72
|
+
opts,
|
|
73
|
+
ok,
|
|
74
|
+
exit_code_recommended,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type LintIssue = {
|
|
2
|
+
code: string;
|
|
3
|
+
message: string;
|
|
4
|
+
path?: string;
|
|
5
|
+
};
|
|
6
|
+
export type LintReportV0 = {
|
|
7
|
+
profile_version: "v0";
|
|
8
|
+
ok: boolean;
|
|
9
|
+
issues: LintIssue[];
|
|
10
|
+
summary: string;
|
|
11
|
+
};
|
|
12
|
+
type LintOptions = {
|
|
13
|
+
outRoot: string;
|
|
14
|
+
emitProfile: boolean;
|
|
15
|
+
mcpExpected: boolean;
|
|
16
|
+
};
|
|
17
|
+
export declare function lintV0(opts: LintOptions): Promise<LintReportV0>;
|
|
18
|
+
export {};
|