intentdna 1.7.4 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/LICENSE +201 -0
- package/README.md +143 -194
- package/dist/assets/candidates.d.ts +124 -0
- package/dist/assets/candidates.js +1249 -0
- package/dist/assets/catalog.d.ts +101 -0
- package/dist/assets/catalog.js +311 -0
- package/dist/assets/evidence.d.ts +93 -0
- package/dist/assets/evidence.js +391 -0
- package/dist/assets/index.d.ts +4 -0
- package/dist/assets/index.js +4 -0
- package/dist/assets/metadata.d.ts +19 -0
- package/dist/assets/metadata.js +227 -0
- package/dist/cli/commands/assets.d.ts +65 -0
- package/dist/cli/commands/assets.js +2871 -0
- package/dist/cli/commands/auth.d.ts +322 -0
- package/dist/cli/commands/auth.js +1592 -0
- package/dist/cli/commands/cloud-auth-guidance.d.ts +2 -0
- package/dist/cli/commands/cloud-auth-guidance.js +4 -0
- package/dist/cli/commands/cloud.d.ts +1359 -0
- package/dist/cli/commands/cloud.js +8364 -0
- package/dist/cli/commands/evolve.js +18 -2
- package/dist/cli/commands/generate.js +25 -2
- package/dist/cli/commands/guard.d.ts +1 -1
- package/dist/cli/commands/guard.js +1 -1
- package/dist/cli/commands/guide.d.ts +15 -0
- package/dist/cli/commands/guide.js +1747 -0
- package/dist/cli/commands/init.d.ts +12 -0
- package/dist/cli/commands/init.js +95 -60
- package/dist/cli/commands/org.d.ts +16 -0
- package/dist/cli/commands/org.js +2393 -0
- package/dist/cli/commands/release.d.ts +59 -0
- package/dist/cli/commands/release.js +716 -0
- package/dist/cli/commands/report.d.ts +82 -0
- package/dist/cli/commands/report.js +1516 -0
- package/dist/cli/commands/run.d.ts +6 -1
- package/dist/cli/commands/run.js +95 -26
- package/dist/cli/commands/sync.d.ts +8 -1
- package/dist/cli/commands/sync.js +436 -141
- package/dist/cli/commands/templates.d.ts +64 -0
- package/dist/cli/commands/templates.js +2151 -1
- package/dist/cli/commands/workflow.d.ts +26 -0
- package/dist/cli/commands/workflow.js +3053 -0
- package/dist/cli/index.js +719 -42
- package/dist/compiler/cascade.js +4 -9
- package/dist/compiler/compile.js +1 -0
- package/dist/governance/evidence-capture-attribution.d.ts +12 -0
- package/dist/governance/evidence-capture-attribution.js +46 -0
- package/dist/governance/index.d.ts +2 -0
- package/dist/governance/index.js +1 -0
- package/dist/hooks/cli.d.ts +6 -0
- package/dist/hooks/cli.js +168 -12
- package/dist/hooks/enforce.d.ts +1 -0
- package/dist/hooks/protocol.d.ts +1 -1
- package/dist/hooks/protocol.js +1 -1
- package/dist/hooks/state.d.ts +41 -0
- package/dist/hooks/state.js +126 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/mcp/tools-state.js +2 -0
- package/dist/organization/index.d.ts +121 -0
- package/dist/organization/index.js +413 -0
- package/dist/runtime/claude-sync-target.d.ts +49 -0
- package/dist/runtime/claude-sync-target.js +280 -0
- package/dist/runtime/codex-adapter.d.ts +254 -0
- package/dist/runtime/codex-adapter.js +1204 -0
- package/dist/runtime/codex-sync-target.d.ts +33 -0
- package/dist/runtime/codex-sync-target.js +241 -0
- package/dist/runtime/index.d.ts +8 -0
- package/dist/runtime/index.js +4 -0
- package/dist/runtime/output-artifact-registry.d.ts +1 -1
- package/dist/runtime/output-artifact-registry.js +2 -0
- package/dist/runtime/settings-adapter.d.ts +1 -1
- package/dist/runtime/settings-adapter.js +1 -1
- package/dist/runtime/skill-adapter.d.ts +5 -1
- package/dist/runtime/skill-adapter.js +120 -34
- package/dist/runtime/sync-artifact-registry.d.ts +99 -0
- package/dist/runtime/sync-artifact-registry.js +194 -0
- package/dist/runtime/sync-target-plan.d.ts +45 -0
- package/dist/runtime/sync-target-plan.js +19 -0
- package/dist/schema/controller-registry.d.ts +8 -1
- package/dist/schema/controller-registry.js +8 -0
- package/dist/schema/types.d.ts +35 -23
- package/dist/schema/types.js +10 -1
- package/dist/schema/validate.js +22 -22
- package/dist/schema/validators/controllers.js +38 -20
- package/dist/schema/workflow-authoring-contract.d.ts +29 -0
- package/dist/schema/workflow-authoring-contract.js +109 -0
- package/dist/schema/yaml-parser.js +18 -0
- package/dist/templates/api-backend.dna.yaml +19 -0
- package/dist/templates/brainstorming-first.dna.yaml +19 -0
- package/dist/templates/code-cleanup.dna.yaml +19 -0
- package/dist/templates/code-review-pipeline.dna.yaml +18 -0
- package/dist/templates/documentation-writer.dna.yaml +19 -0
- package/dist/templates/flutter-behavior-lock.dna.yaml +19 -0
- package/dist/templates/flutter-refactoring-rescue.dna.yaml +19 -0
- package/dist/templates/flutter-rewrite-diagnosis-review.dna.yaml +210 -0
- package/dist/templates/flutter-rewrite-fix-review-loop.dna.yaml +260 -0
- package/dist/templates/flutter-rewrite-new.dna.yaml +506 -0
- package/dist/templates/flutter-rewrite.dna.yaml +20 -0
- package/dist/templates/frontend-quality.dna.yaml +19 -0
- package/dist/templates/full-pipeline.dna.yaml +20 -0
- package/dist/templates/marketplace.d.ts +92 -0
- package/dist/templates/marketplace.js +502 -0
- package/dist/templates/metadata.d.ts +25 -0
- package/dist/templates/metadata.js +136 -3
- package/dist/templates/mobile-dev.dna.yaml +19 -0
- package/dist/templates/multi-agent-handoff-coordination.dna.yaml +156 -0
- package/dist/templates/multi-perspective-review.dna.yaml +19 -0
- package/dist/templates/persistent-executor.dna.yaml +19 -0
- package/dist/templates/qa-loop.dna.yaml +18 -0
- package/dist/templates/release-evidence-gate.dna.yaml +131 -0
- package/dist/templates/requirements-gate.dna.yaml +19 -0
- package/dist/templates/research-orchestration.dna.yaml +19 -0
- package/dist/templates/root-cause-analysis.dna.yaml +19 -0
- package/dist/templates/safe-refactoring.dna.yaml +321 -0
- package/dist/templates/secure-dev.dna.yaml +19 -0
- package/dist/templates/subagent-parallel.dna.yaml +19 -0
- package/dist/templates/systematic-debugging.dna.yaml +19 -0
- package/dist/templates/tdd-strict.dna.yaml +19 -0
- package/dist/templates/ui-visual-regression-qa.dna.yaml +133 -0
- package/dist/templates/workflow-author.dna.yaml +302 -0
- package/dist/templates/workflow-blocked-scope-triage.dna.yaml +242 -0
- package/dist/templates/workflow-completion-audit.dna.yaml +254 -0
- package/dist/templates/workflow-cross-boundary-resource-approval.dna.yaml +249 -0
- package/dist/templates/workflow-evidence-review.dna.yaml +198 -0
- package/dist/templates/workflow-module-inventory.dna.yaml +257 -0
- package/dist/templates/workflow-runtime-smoke-review.dna.yaml +252 -0
- package/dist/templates/workflow-verification-command-routing.dna.yaml +278 -0
- package/dist/workflow/authoring-packet.d.ts +67 -0
- package/dist/workflow/authoring-packet.js +218 -0
- package/dist/workflow/draft.d.ts +159 -0
- package/dist/workflow/draft.js +800 -0
- package/dist/workflow/index.d.ts +3 -0
- package/dist/workflow/index.js +3 -0
- package/dist/workflow/suggestion-review.d.ts +65 -0
- package/dist/workflow/suggestion-review.js +245 -0
- package/package.json +1 -1
- package/spec/README.md +15 -48
- package/spec/codex-adapter-contract.md +176 -0
- package/spec/sync-target-plan.md +158 -0
- package/spec/workflow-capability-ir.md +153 -0
- package/spec/agent-dispatch-fix.md +0 -116
- package/spec/control-plane-convergence-handoff-2026-04-24.md +0 -432
- package/spec/flutter-rewrite-template-optimization.md +0 -301
- package/spec/foundation-hardening.md +0 -178
- package/spec/hooks-infra-harness-hardening.md +0 -741
- package/spec/multi-config.md +0 -172
- package/spec/parallel-isolation.md +0 -268
- package/spec/template-version-namespace-fix.md +0 -653
- package/spec/workflow-pipeline.md +0 -101
|
@@ -20,14 +20,19 @@ import { findTemplateCatalogEntry } from "../../templates/catalog.js";
|
|
|
20
20
|
import { compileToMarkdown, injectIntoFile, removeFromFile } from "../../runtime/markdown.js";
|
|
21
21
|
import { syncInjectMarkdownTargets } from "../../runtime/markdown-target-registry.js";
|
|
22
22
|
import { getSyncOutputArtifactMetadata } from "../../runtime/output-artifact-registry.js";
|
|
23
|
+
import { inspectSyncArtifactRegistry, removeSyncArtifacts, writeSyncArtifactRegistry, } from "../../runtime/sync-artifact-registry.js";
|
|
23
24
|
import { compileWorkflow } from "../../compiler/workflow.js";
|
|
24
25
|
import { compileAllRolesToAgentMD, writeAgentMDFiles, removeAgentMDFiles } from "../../runtime/agent-md.js";
|
|
26
|
+
import { createClaudeCoreSyncTargetPlan, executeClaudeCoreSyncTargetPlan, formatClaudeCoreSyncTargetPlanDryRun, removeLegacyBashHooks } from "../../runtime/claude-sync-target.js";
|
|
25
27
|
import { removeWorkflowScripts } from "../../runtime/workflow-runner.js";
|
|
26
28
|
import { compileWorkflowToSkill, compileControllerToSkill, writeSkillFiles, removeSkillFiles } from "../../runtime/skill-adapter.js";
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
+
import { getControllerKindMetadata } from "../../schema/controller-registry.js";
|
|
30
|
+
import { createCodexSyncTargetPlan, executeCodexSyncTargetPlan, formatCodexSyncTargetPlanSummary, removeCodexSyncTargetArtifacts } from "../../runtime/codex-sync-target.js";
|
|
31
|
+
import { trackedOutputsForSyncTargetPlan } from "../../runtime/sync-target-plan.js";
|
|
32
|
+
import { getPackageVersion } from "../util/version.js";
|
|
29
33
|
import { cascadeDNA } from "../../compiler/cascade.js";
|
|
30
34
|
import { EvolutionEngine } from "../../evolution/index.js";
|
|
35
|
+
import { readOrganizationProfile, reviewOrganization } from "../../organization/index.js";
|
|
31
36
|
const VALID_TARGETS = syncInjectMarkdownTargets();
|
|
32
37
|
/**
|
|
33
38
|
* Detect whether intentdna is running as a Claude Code plugin or standalone binary.
|
|
@@ -66,33 +71,6 @@ async function fileExists(path) {
|
|
|
66
71
|
}
|
|
67
72
|
}
|
|
68
73
|
const DNA_SENTINEL = "intentdna:managed";
|
|
69
|
-
const LEGACY_BASH_HOOKS = [
|
|
70
|
-
"dna-pre-tool-use.sh",
|
|
71
|
-
"dna-post-tool-use.sh",
|
|
72
|
-
"dna-user-prompt-submit.sh",
|
|
73
|
-
"dna-subagent-stop.sh",
|
|
74
|
-
"dna-pre-compact.sh",
|
|
75
|
-
"dna-notification.sh",
|
|
76
|
-
];
|
|
77
|
-
/**
|
|
78
|
-
* Remove legacy bash hook scripts written by previous Intent DNA versions.
|
|
79
|
-
* Only removes files that contain the sentinel comment.
|
|
80
|
-
*/
|
|
81
|
-
async function removeLegacyBashHooks(hooksDir) {
|
|
82
|
-
let removed = 0;
|
|
83
|
-
for (const file of LEGACY_BASH_HOOKS) {
|
|
84
|
-
const path = resolve(hooksDir, file);
|
|
85
|
-
try {
|
|
86
|
-
const content = await readFile(path, "utf-8");
|
|
87
|
-
if (content.includes("intentdna:managed")) {
|
|
88
|
-
await unlink(path);
|
|
89
|
-
removed++;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
catch { /* File doesn't exist, skip */ }
|
|
93
|
-
}
|
|
94
|
-
return removed;
|
|
95
|
-
}
|
|
96
74
|
/**
|
|
97
75
|
* Remove stale DNA-managed files from a directory.
|
|
98
76
|
* Only removes files matching activeNamespaces (dna-{ns}-*).
|
|
@@ -193,8 +171,8 @@ async function resolveVariables(rawVars, configPath, options) {
|
|
|
193
171
|
* Auto-detect DNA config file in current directory (legacy single-config).
|
|
194
172
|
* Priority: .dna/config.yaml > .dna/config.yml > .dna/config.json > .dna.yaml > .dna.yml > .dna.json
|
|
195
173
|
*/
|
|
196
|
-
export async function autoDetectConfigs() {
|
|
197
|
-
return detectDNAConfigs(
|
|
174
|
+
export async function autoDetectConfigs(projectDir = process.cwd()) {
|
|
175
|
+
return detectDNAConfigs(projectDir);
|
|
198
176
|
}
|
|
199
177
|
/**
|
|
200
178
|
* Read the current package version from package.json.
|
|
@@ -292,11 +270,32 @@ async function computeFileHash(filePath) {
|
|
|
292
270
|
const content = await readFile(filePath, "utf-8");
|
|
293
271
|
return fileSha256(content);
|
|
294
272
|
}
|
|
295
|
-
function trackSyncOutput(trackedOutputs, artifact, path) {
|
|
273
|
+
function trackSyncOutput(trackedOutputs, artifact, path, metadata) {
|
|
296
274
|
if (!getSyncOutputArtifactMetadata(artifact)) {
|
|
297
275
|
throw new Error(`Unknown sync output artifact: ${artifact}`);
|
|
298
276
|
}
|
|
299
|
-
trackedOutputs.push({ artifact, path
|
|
277
|
+
trackedOutputs.push({ artifact, path, ...metadata });
|
|
278
|
+
}
|
|
279
|
+
function trackSyncTargetPlanOutputs(trackedOutputs, plan, projectDir) {
|
|
280
|
+
const installMode = "hookInstallMode" in plan && typeof plan.hookInstallMode === "string"
|
|
281
|
+
? plan.hookInstallMode
|
|
282
|
+
: undefined;
|
|
283
|
+
for (const output of trackedOutputsForSyncTargetPlan(plan)) {
|
|
284
|
+
const planned = plan.plannedArtifacts.find((artifact) => {
|
|
285
|
+
if (artifact.outputArtifact !== output.artifact)
|
|
286
|
+
return false;
|
|
287
|
+
return artifact.path === output.path || resolve(artifact.path) === resolve(output.path);
|
|
288
|
+
});
|
|
289
|
+
const outputPath = resolve(projectDir, output.path);
|
|
290
|
+
trackSyncOutput(trackedOutputs, output.artifact, outputPath, {
|
|
291
|
+
kind: planned?.kind,
|
|
292
|
+
target: plan.target,
|
|
293
|
+
adapter: plan.target,
|
|
294
|
+
installMode,
|
|
295
|
+
ownership: planned?.ownership,
|
|
296
|
+
trackedByLock: planned?.trackedByLock,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
300
299
|
}
|
|
301
300
|
function summarizeSyncOutputs(trackedOutputs, dryRun) {
|
|
302
301
|
if (trackedOutputs.length === 0)
|
|
@@ -318,6 +317,122 @@ function lockTrackedOutputPaths(trackedOutputs) {
|
|
|
318
317
|
.filter((output) => getSyncOutputArtifactMetadata(output.artifact)?.trackedByLock)
|
|
319
318
|
.map((output) => output.path);
|
|
320
319
|
}
|
|
320
|
+
function buildSyncRunJsonResult(opts, projectDir, files, trackedOutputs, registryArtifacts) {
|
|
321
|
+
const targets = [...new Set(trackedOutputs.map((output) => output.adapter ?? output.target ?? "generic"))].sort();
|
|
322
|
+
return {
|
|
323
|
+
schema_version: "intentdna.sync_result.v1",
|
|
324
|
+
mode: "sync",
|
|
325
|
+
status: opts.dryRun ? "dry_run" : "synced",
|
|
326
|
+
dryRun: opts.dryRun === true,
|
|
327
|
+
projectDir,
|
|
328
|
+
files,
|
|
329
|
+
commandEffects: {
|
|
330
|
+
mutation_performed: opts.dryRun !== true,
|
|
331
|
+
network_performed: false,
|
|
332
|
+
runtime_authority: false,
|
|
333
|
+
syncs_harness: opts.dryRun !== true,
|
|
334
|
+
executes_workflow: false,
|
|
335
|
+
},
|
|
336
|
+
targets,
|
|
337
|
+
counts: {
|
|
338
|
+
tracked_outputs: trackedOutputs.length,
|
|
339
|
+
lock_tracked_outputs: lockTrackedOutputPaths(trackedOutputs).length,
|
|
340
|
+
registry_artifacts: registryArtifacts,
|
|
341
|
+
},
|
|
342
|
+
outputs: trackedOutputs.map((output) => {
|
|
343
|
+
const metadata = getSyncOutputArtifactMetadata(output.artifact);
|
|
344
|
+
return {
|
|
345
|
+
artifact: output.artifact,
|
|
346
|
+
label: metadata?.label ?? output.artifact,
|
|
347
|
+
path: output.path,
|
|
348
|
+
kind: output.kind,
|
|
349
|
+
target: output.target,
|
|
350
|
+
adapter: output.adapter,
|
|
351
|
+
installMode: output.installMode,
|
|
352
|
+
ownership: output.ownership,
|
|
353
|
+
trackedByLock: metadata?.trackedByLock === true,
|
|
354
|
+
};
|
|
355
|
+
}),
|
|
356
|
+
registryPath: opts.dryRun ? undefined : resolve(projectDir, ".dna", "sync", "registry.json"),
|
|
357
|
+
boundaries: [
|
|
358
|
+
"dna sync installs, distributes, and injects local artifacts only; it does not execute workflows.",
|
|
359
|
+
"dna-hook and local runtime evidence remain runtime truth for allow/warn/block/validate decisions.",
|
|
360
|
+
"Online and marketplace surfaces do not become runtime authority from sync output.",
|
|
361
|
+
"Network upload, Supabase deployment, and trusted cloud ingestion are not performed by dna sync.",
|
|
362
|
+
],
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
function canonicalOrganizationSyncTarget(target) {
|
|
366
|
+
const normalized = target.toLowerCase();
|
|
367
|
+
if (normalized === "codex" || normalized === "codex_cli")
|
|
368
|
+
return "codex";
|
|
369
|
+
if (normalized === "claude" || normalized === "claude_code")
|
|
370
|
+
return "claude";
|
|
371
|
+
return normalized;
|
|
372
|
+
}
|
|
373
|
+
function requestedOrganizationSyncTargets(opts) {
|
|
374
|
+
const targets = new Set();
|
|
375
|
+
if (opts.codex)
|
|
376
|
+
targets.add("codex");
|
|
377
|
+
if (opts.inject || opts.hooksDir || opts.agentsDir || opts.workflowDir || opts.skillsDir || opts.settingsPath) {
|
|
378
|
+
targets.add("claude");
|
|
379
|
+
}
|
|
380
|
+
return targets;
|
|
381
|
+
}
|
|
382
|
+
function syncTargetMatches(requestedTargets, actualTargets) {
|
|
383
|
+
return actualTargets.some((target) => requestedTargets.has(canonicalOrganizationSyncTarget(target)));
|
|
384
|
+
}
|
|
385
|
+
async function writeOrganizationRolloutGateIfBlocked(opts, projectDir) {
|
|
386
|
+
const requestedTargets = requestedOrganizationSyncTargets(opts);
|
|
387
|
+
if (requestedTargets.size === 0)
|
|
388
|
+
return;
|
|
389
|
+
const profile = await readOrganizationProfile({ projectDir });
|
|
390
|
+
if (!profile.profile)
|
|
391
|
+
return;
|
|
392
|
+
const profileTargets = profile.profile.projects.flatMap((project) => project.syncTargets);
|
|
393
|
+
if (!syncTargetMatches(requestedTargets, profileTargets))
|
|
394
|
+
return;
|
|
395
|
+
const review = await reviewOrganization({ projectDir });
|
|
396
|
+
if (!review)
|
|
397
|
+
return;
|
|
398
|
+
const blockedGates = review.projects.flatMap((project) => project.assetGates
|
|
399
|
+
.filter((gate) => !gate.syncReady && syncTargetMatches(requestedTargets, gate.syncTargets))
|
|
400
|
+
.map((gate) => ({
|
|
401
|
+
project: project.name,
|
|
402
|
+
asset: gate.asset,
|
|
403
|
+
evidenceGate: gate.evidenceGate,
|
|
404
|
+
syncTargets: gate.syncTargets,
|
|
405
|
+
nextCommand: gate.nextCommand,
|
|
406
|
+
reason: gate.reason,
|
|
407
|
+
})));
|
|
408
|
+
if (blockedGates.length === 0)
|
|
409
|
+
return;
|
|
410
|
+
const blockedTargets = [...new Set(blockedGates.flatMap((gate) => gate.syncTargets
|
|
411
|
+
.map(canonicalOrganizationSyncTarget)
|
|
412
|
+
.filter((target) => requestedTargets.has(target))))].sort().join(", ");
|
|
413
|
+
const primaryCommand = organizationPrimarySyncBlockCommand(blockedGates);
|
|
414
|
+
process.stderr.write("Organization rollout gate:\n");
|
|
415
|
+
process.stderr.write(` Status: blocked for ${blockedTargets}\n`);
|
|
416
|
+
process.stderr.write(` Primary command: ${primaryCommand}\n`);
|
|
417
|
+
process.stderr.write(" Blocked assets:\n");
|
|
418
|
+
for (const gate of blockedGates.slice(0, 5)) {
|
|
419
|
+
process.stderr.write(` - ${gate.project}/${gate.asset}: ${gate.reason}\n`);
|
|
420
|
+
process.stderr.write(` next: ${gate.nextCommand}\n`);
|
|
421
|
+
}
|
|
422
|
+
process.stderr.write(` Resolve before sync: ${primaryCommand}\n`);
|
|
423
|
+
process.stderr.write(" Recheck before sync: dna org rollout\n");
|
|
424
|
+
process.stderr.write(" Boundary: adapter output below is diagnostic until organization rollout shows the target ready.\n");
|
|
425
|
+
}
|
|
426
|
+
function organizationPrimarySyncBlockCommand(blockedGates) {
|
|
427
|
+
const hasOpenEvidence = blockedGates.some((gate) => gate.evidenceGate === "open");
|
|
428
|
+
if (hasOpenEvidence)
|
|
429
|
+
return blockedGates[0]?.nextCommand ?? "dna org review";
|
|
430
|
+
const hasMissingConfiguration = blockedGates.some((gate) => gate.evidenceGate !== "open" &&
|
|
431
|
+
gate.nextCommand.startsWith("dna init --template "));
|
|
432
|
+
if (hasMissingConfiguration)
|
|
433
|
+
return "dna org configure-plan";
|
|
434
|
+
return blockedGates[0]?.nextCommand ?? "dna org review";
|
|
435
|
+
}
|
|
321
436
|
async function generateLockFile(lockPath, dnaSources, outputFiles, provenance, compiledIrHash) {
|
|
322
437
|
const outputs = {};
|
|
323
438
|
for (const f of outputFiles) {
|
|
@@ -333,10 +448,152 @@ async function generateLockFile(lockPath, dnaSources, outputFiles, provenance, c
|
|
|
333
448
|
await mkdir(dirname(lockPath), { recursive: true });
|
|
334
449
|
await writeFileAsync(lockPath, JSON.stringify(lock, null, 2) + "\n", "utf-8");
|
|
335
450
|
}
|
|
451
|
+
function syncRegistryHealth(inspection) {
|
|
452
|
+
if (!inspection.registry)
|
|
453
|
+
return "missing_registry";
|
|
454
|
+
if (inspection.totals.missing > 0 || inspection.totals.drifted > 0 || inspection.totals.untracked > 0) {
|
|
455
|
+
return "attention_required";
|
|
456
|
+
}
|
|
457
|
+
return "ok";
|
|
458
|
+
}
|
|
459
|
+
function syncRegistryCommandEffects() {
|
|
460
|
+
return {
|
|
461
|
+
mutation_performed: false,
|
|
462
|
+
network_performed: false,
|
|
463
|
+
runtime_authority: false,
|
|
464
|
+
syncs_harness: false,
|
|
465
|
+
executes_workflow: false,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
function syncRegistryBoundaries() {
|
|
469
|
+
return [
|
|
470
|
+
"dna sync status and doctor inspect local registry artifacts only.",
|
|
471
|
+
"They do not execute workflows or make allow/warn/block/validate decisions.",
|
|
472
|
+
"They do not call Online, Supabase, marketplace, or hidden transport.",
|
|
473
|
+
"Runtime truth remains local dna/dna-hook and the synced harness artifacts.",
|
|
474
|
+
];
|
|
475
|
+
}
|
|
476
|
+
function syncRegistryNextCommands(status) {
|
|
477
|
+
if (status === "missing_registry")
|
|
478
|
+
return ["dna sync --dry-run", "dna sync"];
|
|
479
|
+
if (status === "attention_required")
|
|
480
|
+
return ["dna sync", "dna sync doctor", "dna sync remove --dry-run"];
|
|
481
|
+
return ["dna sync doctor", "dna sync remove --dry-run"];
|
|
482
|
+
}
|
|
483
|
+
function printSyncRegistryStatus(json = false, projectDir = process.cwd()) {
|
|
484
|
+
return inspectSyncArtifactRegistry(projectDir).then((inspection) => {
|
|
485
|
+
if (json) {
|
|
486
|
+
const status = syncRegistryHealth(inspection);
|
|
487
|
+
const packet = {
|
|
488
|
+
schema_version: "intentdna.sync_status.v1",
|
|
489
|
+
mode: "sync_registry_status",
|
|
490
|
+
status,
|
|
491
|
+
ok: status === "ok",
|
|
492
|
+
projectDir,
|
|
493
|
+
commandEffects: syncRegistryCommandEffects(),
|
|
494
|
+
boundaries: syncRegistryBoundaries(),
|
|
495
|
+
nextCommands: syncRegistryNextCommands(status),
|
|
496
|
+
...inspection,
|
|
497
|
+
};
|
|
498
|
+
process.stdout.write(JSON.stringify(packet, null, 2) + "\n");
|
|
499
|
+
return inspection.registry ? 0 : 1;
|
|
500
|
+
}
|
|
501
|
+
if (!inspection.registry) {
|
|
502
|
+
process.stderr.write(`No sync artifact registry found at ${inspection.registryPath}\n`);
|
|
503
|
+
process.stderr.write("Run `dna sync` to install runtime artifacts before checking status.\n");
|
|
504
|
+
return 1;
|
|
505
|
+
}
|
|
506
|
+
process.stderr.write("IntentDNA sync artifact status\n");
|
|
507
|
+
process.stderr.write(`Registry: ${inspection.registryPath}\n`);
|
|
508
|
+
process.stderr.write(`Synced: ${inspection.registry.syncedAt}\n`);
|
|
509
|
+
process.stderr.write(`DNA sources: ${inspection.registry.template.sourceDnaIds.join(", ") || "unknown"}\n`);
|
|
510
|
+
process.stderr.write(`Artifacts: ${inspection.totals.artifacts} total, ${inspection.totals.ok} ok, ${inspection.totals.missing} missing, ${inspection.totals.drifted} drifted, ${inspection.totals.untracked} untracked\n`);
|
|
511
|
+
for (const artifact of inspection.artifacts) {
|
|
512
|
+
process.stderr.write(` - [${artifact.status}] ${artifact.relativePath} (${artifact.kind}, ${artifact.ownership})\n`);
|
|
513
|
+
if (artifact.status !== "ok")
|
|
514
|
+
process.stderr.write(` ${artifact.reason}\n`);
|
|
515
|
+
}
|
|
516
|
+
return inspection.totals.missing > 0 || inspection.totals.drifted > 0 ? 1 : 0;
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
async function runSyncRegistryDoctor(json = false, projectDir = process.cwd()) {
|
|
520
|
+
const inspection = await inspectSyncArtifactRegistry(projectDir);
|
|
521
|
+
const ok = !!inspection.registry && inspection.totals.missing === 0 && inspection.totals.drifted === 0;
|
|
522
|
+
if (json) {
|
|
523
|
+
const status = syncRegistryHealth(inspection);
|
|
524
|
+
const packet = {
|
|
525
|
+
schema_version: "intentdna.sync_doctor.v1",
|
|
526
|
+
mode: "sync_registry_doctor",
|
|
527
|
+
status,
|
|
528
|
+
ok,
|
|
529
|
+
projectDir,
|
|
530
|
+
commandEffects: syncRegistryCommandEffects(),
|
|
531
|
+
boundaries: syncRegistryBoundaries(),
|
|
532
|
+
nextCommands: syncRegistryNextCommands(status),
|
|
533
|
+
...inspection,
|
|
534
|
+
};
|
|
535
|
+
process.stdout.write(JSON.stringify(packet, null, 2) + "\n");
|
|
536
|
+
return ok ? 0 : 1;
|
|
537
|
+
}
|
|
538
|
+
if (!inspection.registry) {
|
|
539
|
+
process.stderr.write(`Sync doctor: no registry found at ${inspection.registryPath}\n`);
|
|
540
|
+
process.stderr.write("Next: run `dna sync` for the target project.\n");
|
|
541
|
+
return 1;
|
|
542
|
+
}
|
|
543
|
+
process.stderr.write(`Sync doctor: ${ok ? "ok" : "attention required"}\n`);
|
|
544
|
+
process.stderr.write(`Registry: ${inspection.registryPath}\n`);
|
|
545
|
+
if (ok) {
|
|
546
|
+
process.stderr.write(`All ${inspection.totals.artifacts} artifact(s) match the last sync.\n`);
|
|
547
|
+
return 0;
|
|
548
|
+
}
|
|
549
|
+
for (const artifact of inspection.artifacts.filter((entry) => entry.status !== "ok")) {
|
|
550
|
+
process.stderr.write(` - [${artifact.status}] ${artifact.relativePath}: ${artifact.reason}\n`);
|
|
551
|
+
}
|
|
552
|
+
process.stderr.write("Next: rerun `dna sync` to repair drift, or `dna sync remove` to clean safe IntentDNA-owned artifacts.\n");
|
|
553
|
+
return 1;
|
|
554
|
+
}
|
|
555
|
+
async function runSyncRegistryRemove(opts) {
|
|
556
|
+
const result = await removeSyncArtifacts({ dryRun: opts.dryRun, projectDir: opts.projectDir });
|
|
557
|
+
if (opts.json) {
|
|
558
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
559
|
+
return 0;
|
|
560
|
+
}
|
|
561
|
+
process.stderr.write(opts.dryRun ? "Dry-run registry remove plan:\n" : "IntentDNA registry remove:\n");
|
|
562
|
+
if (result.removed.length === 0) {
|
|
563
|
+
process.stderr.write(" No safe registry-tracked artifacts removed.\n");
|
|
564
|
+
}
|
|
565
|
+
else {
|
|
566
|
+
for (const path of result.removed)
|
|
567
|
+
process.stderr.write(` removed: ${path}\n`);
|
|
568
|
+
}
|
|
569
|
+
for (const skipped of result.skipped) {
|
|
570
|
+
process.stderr.write(` skipped: ${skipped.path} (${skipped.reason})\n`);
|
|
571
|
+
}
|
|
572
|
+
if (result.preservation.kept.length > 0) {
|
|
573
|
+
process.stderr.write("Preserved files:\n");
|
|
574
|
+
for (const kept of result.preservation.kept) {
|
|
575
|
+
process.stderr.write(` - ${kept.path}\n`);
|
|
576
|
+
process.stderr.write(` reason: ${kept.reason}\n`);
|
|
577
|
+
process.stderr.write(` why: ${kept.explanation}\n`);
|
|
578
|
+
}
|
|
579
|
+
process.stderr.write("Next safe checks:\n");
|
|
580
|
+
for (const command of result.preservation.nextCommands)
|
|
581
|
+
process.stderr.write(` - ${command}\n`);
|
|
582
|
+
}
|
|
583
|
+
process.stderr.write(`Boundary: ${result.boundary}.\n`);
|
|
584
|
+
return 0;
|
|
585
|
+
}
|
|
336
586
|
export async function runSync(opts) {
|
|
587
|
+
const projectDir = resolve(opts.projectDir ?? process.cwd());
|
|
588
|
+
if (opts.action === "status")
|
|
589
|
+
return printSyncRegistryStatus(opts.json, projectDir);
|
|
590
|
+
if (opts.action === "doctor")
|
|
591
|
+
return runSyncRegistryDoctor(opts.json, projectDir);
|
|
592
|
+
if (opts.action === "remove-registry")
|
|
593
|
+
return runSyncRegistryRemove({ ...opts, projectDir });
|
|
337
594
|
// ── Auto-detect mode: zero-config when no files/outputs specified ──
|
|
338
595
|
if (!opts.remove && opts.files.length === 0) {
|
|
339
|
-
const autoFiles = await autoDetectConfigs();
|
|
596
|
+
const autoFiles = await autoDetectConfigs(projectDir);
|
|
340
597
|
if (autoFiles.length > 0) {
|
|
341
598
|
opts.files = autoFiles;
|
|
342
599
|
if (autoFiles.length === 1) {
|
|
@@ -351,16 +608,19 @@ export async function runSync(opts) {
|
|
|
351
608
|
}
|
|
352
609
|
}
|
|
353
610
|
}
|
|
354
|
-
if (!opts.remove &&
|
|
611
|
+
if (!opts.remove && opts.codex && !opts.skillsDir) {
|
|
612
|
+
opts.skillsDir = resolve(projectDir, ".agents", "skills");
|
|
613
|
+
process.stderr.write("Codex workflow projection: .agents/skills\n");
|
|
614
|
+
}
|
|
615
|
+
if (!opts.remove && !opts.inject && !opts.hooksDir && !opts.agentsDir && !opts.workflowDir && !opts.skillsDir && !opts.settingsPath && !opts.codex) {
|
|
355
616
|
// Auto-detect Claude Code environment → fill all outputs
|
|
356
|
-
const
|
|
357
|
-
const
|
|
358
|
-
const hasClaudeMd = await fileExists(resolve(cwd, "CLAUDE.md"));
|
|
617
|
+
const hasClaudeDir = await fileExists(resolve(projectDir, ".claude"));
|
|
618
|
+
const hasClaudeMd = await fileExists(resolve(projectDir, "CLAUDE.md"));
|
|
359
619
|
if (hasClaudeDir || hasClaudeMd || opts.files.length > 0) {
|
|
360
|
-
opts.inject = resolve(
|
|
361
|
-
opts.agentsDir = resolve(
|
|
362
|
-
opts.skillsDir = resolve(
|
|
363
|
-
opts.settingsPath = resolve(
|
|
620
|
+
opts.inject = resolve(projectDir, "CLAUDE.md");
|
|
621
|
+
opts.agentsDir = resolve(projectDir, ".claude", "agents");
|
|
622
|
+
opts.skillsDir = resolve(projectDir, ".claude", "skills");
|
|
623
|
+
opts.settingsPath = resolve(projectDir, ".claude", "settings.json");
|
|
364
624
|
process.stderr.write(`Auto-detect: Claude Code → CLAUDE.md + agents + skills + settings\n`);
|
|
365
625
|
}
|
|
366
626
|
}
|
|
@@ -373,8 +633,8 @@ export async function runSync(opts) {
|
|
|
373
633
|
opts.plugin = resolvedMode === "plugin";
|
|
374
634
|
// Handle --remove
|
|
375
635
|
if (opts.remove) {
|
|
376
|
-
if (!opts.inject && !opts.hooksDir && !opts.agentsDir && !opts.workflowDir && !opts.skillsDir) {
|
|
377
|
-
process.stderr.write("Error: --inject, --hooks, --agents, --workflow, or --
|
|
636
|
+
if (!opts.inject && !opts.hooksDir && !opts.agentsDir && !opts.workflowDir && !opts.skillsDir && !opts.codex) {
|
|
637
|
+
process.stderr.write("Error: --inject, --hooks, --agents, --workflow, --skills, or --codex is required with --remove\n");
|
|
378
638
|
return 2;
|
|
379
639
|
}
|
|
380
640
|
if (opts.inject) {
|
|
@@ -387,9 +647,9 @@ export async function runSync(opts) {
|
|
|
387
647
|
}
|
|
388
648
|
}
|
|
389
649
|
if (opts.hooksDir) {
|
|
390
|
-
const
|
|
391
|
-
if (
|
|
392
|
-
process.stderr.write(`Removed ${
|
|
650
|
+
const removed = await removeLegacyBashHooks(opts.hooksDir);
|
|
651
|
+
if (removed.length > 0) {
|
|
652
|
+
process.stderr.write(`Removed ${removed.length} legacy bash hook(s) from ${opts.hooksDir}\n`);
|
|
393
653
|
}
|
|
394
654
|
else {
|
|
395
655
|
process.stderr.write(`No legacy bash hooks found in ${opts.hooksDir}\n`);
|
|
@@ -422,14 +682,23 @@ export async function runSync(opts) {
|
|
|
422
682
|
process.stderr.write(`No Intent DNA skill dirs found in ${opts.skillsDir}\n`);
|
|
423
683
|
}
|
|
424
684
|
}
|
|
685
|
+
if (opts.codex) {
|
|
686
|
+
const removed = await removeCodexSyncTargetArtifacts(projectDir);
|
|
687
|
+
if (removed.length > 0) {
|
|
688
|
+
process.stderr.write(`Removed ${removed.length} Intent DNA Codex adapter artifact(s)\n`);
|
|
689
|
+
}
|
|
690
|
+
else {
|
|
691
|
+
process.stderr.write("No Intent DNA Codex adapter artifacts found\n");
|
|
692
|
+
}
|
|
693
|
+
}
|
|
425
694
|
return 0;
|
|
426
695
|
}
|
|
427
696
|
if (opts.files.length === 0) {
|
|
428
697
|
process.stderr.write("Error: no DNA files specified\n");
|
|
429
698
|
return 2;
|
|
430
699
|
}
|
|
431
|
-
if (!opts.inject && !opts.hooksDir && !opts.agentsDir && !opts.workflowDir && !opts.skillsDir && !opts.settingsPath) {
|
|
432
|
-
process.stderr.write("Error: --inject <path>, --hooks <dir>, --agents <dir>, --workflow <dir>, --skills <dir>,
|
|
700
|
+
if (!opts.inject && !opts.hooksDir && !opts.agentsDir && !opts.workflowDir && !opts.skillsDir && !opts.settingsPath && !opts.codex) {
|
|
701
|
+
process.stderr.write("Error: --inject <path>, --hooks <dir>, --agents <dir>, --workflow <dir>, --skills <dir>, --settings <path>, or --codex is required\n");
|
|
433
702
|
return 2;
|
|
434
703
|
}
|
|
435
704
|
if (opts.inject && !VALID_TARGETS.includes(opts.target)) {
|
|
@@ -437,6 +706,22 @@ export async function runSync(opts) {
|
|
|
437
706
|
return 2;
|
|
438
707
|
}
|
|
439
708
|
const target = opts.target;
|
|
709
|
+
opts.files = opts.files.map((file) => resolve(projectDir, file));
|
|
710
|
+
if (opts.inject)
|
|
711
|
+
opts.inject = resolve(projectDir, opts.inject);
|
|
712
|
+
if (opts.hooksDir)
|
|
713
|
+
opts.hooksDir = resolve(projectDir, opts.hooksDir);
|
|
714
|
+
if (opts.agentsDir)
|
|
715
|
+
opts.agentsDir = resolve(projectDir, opts.agentsDir);
|
|
716
|
+
if (opts.workflowDir)
|
|
717
|
+
opts.workflowDir = resolve(projectDir, opts.workflowDir);
|
|
718
|
+
if (opts.skillsDir)
|
|
719
|
+
opts.skillsDir = resolve(projectDir, opts.skillsDir);
|
|
720
|
+
if (opts.settingsPath)
|
|
721
|
+
opts.settingsPath = resolve(projectDir, opts.settingsPath);
|
|
722
|
+
if (opts.lockFile)
|
|
723
|
+
opts.lockFile = resolve(projectDir, opts.lockFile);
|
|
724
|
+
await writeOrganizationRolloutGateIfBlocked(opts, projectDir);
|
|
440
725
|
try {
|
|
441
726
|
// Track all output file paths for lock file generation
|
|
442
727
|
const trackedOutputs = [];
|
|
@@ -489,65 +774,32 @@ export async function runSync(opts) {
|
|
|
489
774
|
process.stderr.write(`No changes needed in ${opts.inject}\n`);
|
|
490
775
|
}
|
|
491
776
|
}
|
|
492
|
-
trackSyncOutput(trackedOutputs, "markdown-injection", opts.inject
|
|
777
|
+
trackSyncOutput(trackedOutputs, "markdown-injection", opts.inject, {
|
|
778
|
+
kind: "markdown_injection",
|
|
779
|
+
target: "markdown",
|
|
780
|
+
ownership: "owned_block",
|
|
781
|
+
trackedByLock: true,
|
|
782
|
+
});
|
|
493
783
|
}
|
|
494
|
-
// Step 3.5:
|
|
784
|
+
// Step 3.5: Execute Claude Code core target plan (compiled IR, MCP, settings hooks)
|
|
495
785
|
{
|
|
496
|
-
const
|
|
497
|
-
const
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
process.stderr.write(`Dry-run: would write compiled IR: ${irPath}\n`);
|
|
505
|
-
}
|
|
506
|
-
else {
|
|
507
|
-
const writtenIrPath = await writeCompiledIR(cwd, compiled);
|
|
508
|
-
process.stderr.write(`Wrote compiled IR: ${writtenIrPath}\n`);
|
|
509
|
-
}
|
|
510
|
-
trackSyncOutput(trackedOutputs, "compiled-ir", irPath);
|
|
511
|
-
// Clean up old bash hooks if they exist
|
|
512
|
-
const oldHooksDir = resolve(cwd, ".claude", "hooks");
|
|
786
|
+
const cascadedForCore = cascadeDNA(loadedDNAs);
|
|
787
|
+
const claudeCorePlan = createClaudeCoreSyncTargetPlan({
|
|
788
|
+
projectDir,
|
|
789
|
+
ir,
|
|
790
|
+
roles: cascadedForCore.roles,
|
|
791
|
+
mode: opts.plugin ? "plugin" : "bin",
|
|
792
|
+
settingsPath: opts.settingsPath,
|
|
793
|
+
});
|
|
513
794
|
if (opts.dryRun) {
|
|
514
|
-
process.stderr.write(
|
|
795
|
+
process.stderr.write(formatClaudeCoreSyncTargetPlanDryRun(claudeCorePlan));
|
|
515
796
|
}
|
|
516
797
|
else {
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
process.stderr.write(
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
// G2/G3: Register built-in DNA MCP server in .claude/.mcp.json
|
|
523
|
-
const mcpPath = resolve(cwd, ".claude", ".mcp.json");
|
|
524
|
-
if (opts.dryRun) {
|
|
525
|
-
process.stderr.write(`Dry-run: would register dna-mcp server in ${mcpPath}\n`);
|
|
798
|
+
const result = await executeClaudeCoreSyncTargetPlan(claudeCorePlan, projectDir);
|
|
799
|
+
for (const message of result.messages)
|
|
800
|
+
process.stderr.write(`${message}\n`);
|
|
526
801
|
}
|
|
527
|
-
|
|
528
|
-
await registerDNAMCPServer(cwd);
|
|
529
|
-
}
|
|
530
|
-
trackSyncOutput(trackedOutputs, "mcp-config", mcpPath);
|
|
531
|
-
}
|
|
532
|
-
// Step 3.6: Register hooks in settings.json (bin mode only)
|
|
533
|
-
// Plugin mode: hooks managed by plugin framework — skip settings.json
|
|
534
|
-
if (!opts.plugin && opts.settingsPath) {
|
|
535
|
-
const enabledEvents = detectEnabledEvents(ir);
|
|
536
|
-
if (enabledEvents.length > 0) {
|
|
537
|
-
const pluginSettings = compilePluginSettings(enabledEvents, 10, recommendHookTimeouts(ir));
|
|
538
|
-
if (opts.dryRun) {
|
|
539
|
-
JSON.stringify(pluginSettings);
|
|
540
|
-
process.stderr.write(`Dry-run: would register ${enabledEvents.length} hook event(s) in ${opts.settingsPath}\n`);
|
|
541
|
-
}
|
|
542
|
-
else {
|
|
543
|
-
await mergeSettingsFile(pluginSettings, opts.settingsPath);
|
|
544
|
-
process.stderr.write(`Bin mode: registered ${enabledEvents.length} hook event(s) in ${opts.settingsPath}\n`);
|
|
545
|
-
}
|
|
546
|
-
trackSyncOutput(trackedOutputs, "settings-hooks", opts.settingsPath);
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
else if (opts.plugin && opts.settingsPath) {
|
|
550
|
-
process.stderr.write(`Plugin mode: hooks managed by plugin framework (skipping settings.json)\n`);
|
|
802
|
+
trackSyncTargetPlanOutputs(trackedOutputs, claudeCorePlan, projectDir);
|
|
551
803
|
}
|
|
552
804
|
// Extract active namespaces from loaded DNAs for namespace-scoped cleanup
|
|
553
805
|
const activeNamespaces = loadedDNAs
|
|
@@ -572,7 +824,8 @@ export async function runSync(opts) {
|
|
|
572
824
|
process.stderr.write(`No roles defined in DNA files\n`);
|
|
573
825
|
}
|
|
574
826
|
else {
|
|
575
|
-
const
|
|
827
|
+
const agentRoles = Object.fromEntries(Object.entries(roles).filter(([, role]) => !["human", "interactive"].includes(role.model?.trim().toLowerCase() ?? "")));
|
|
828
|
+
const results = compileAllRolesToAgentMD(agentRoles, ir);
|
|
576
829
|
const written = results.map((result) => resolve(opts.agentsDir, result.fileName));
|
|
577
830
|
if (opts.dryRun) {
|
|
578
831
|
process.stderr.write(`Dry-run: would generate ${written.length} agent file(s) in ${opts.agentsDir}\n`);
|
|
@@ -585,7 +838,13 @@ export async function runSync(opts) {
|
|
|
585
838
|
}
|
|
586
839
|
}
|
|
587
840
|
for (const file of written)
|
|
588
|
-
trackSyncOutput(trackedOutputs, "agents", file
|
|
841
|
+
trackSyncOutput(trackedOutputs, "agents", file, {
|
|
842
|
+
kind: "agent_md",
|
|
843
|
+
target: "claude_code",
|
|
844
|
+
adapter: "claude_code",
|
|
845
|
+
ownership: "intentdna_owned",
|
|
846
|
+
trackedByLock: true,
|
|
847
|
+
});
|
|
589
848
|
}
|
|
590
849
|
}
|
|
591
850
|
// Step 6: Generate workflow script(s)
|
|
@@ -624,13 +883,19 @@ export async function runSync(opts) {
|
|
|
624
883
|
await writeWorkflowScript(shellResult, opts.workflowDir);
|
|
625
884
|
process.stderr.write(`Generated workflow script: ${written}\n`);
|
|
626
885
|
}
|
|
627
|
-
trackSyncOutput(trackedOutputs, "workflows", written
|
|
886
|
+
trackSyncOutput(trackedOutputs, "workflows", written, {
|
|
887
|
+
kind: "workflow_script",
|
|
888
|
+
target: "generic",
|
|
889
|
+
ownership: "intentdna_owned",
|
|
890
|
+
trackedByLock: true,
|
|
891
|
+
});
|
|
628
892
|
}
|
|
629
893
|
}
|
|
630
894
|
}
|
|
631
895
|
}
|
|
632
896
|
// Step 7: Generate skill files from workflows
|
|
633
897
|
if (opts.skillsDir) {
|
|
898
|
+
const skillSurface = opts.codex ? "codex_cli" : "claude_code";
|
|
634
899
|
// Clean stale DNA-managed skills before regenerating
|
|
635
900
|
if (opts.dryRun) {
|
|
636
901
|
process.stderr.write(`Dry-run: would clean stale skill dir(s) in ${opts.skillsDir}\n`);
|
|
@@ -654,16 +919,15 @@ export async function runSync(opts) {
|
|
|
654
919
|
}
|
|
655
920
|
const configPath = opts.files.length === 1 ? opts.files[0] : null;
|
|
656
921
|
const variables = await resolveVariables(rawVars, configPath, { dryRun: opts.dryRun });
|
|
657
|
-
// Step 8: Generate .claude/.mcp.json
|
|
658
|
-
{
|
|
922
|
+
// Step 8: Generate .claude/.mcp.json only for Claude Code skills.
|
|
923
|
+
if (skillSurface === "claude_code") {
|
|
659
924
|
const mcpDeps = {};
|
|
660
925
|
for (const d of dnas) {
|
|
661
926
|
if (d.mcp)
|
|
662
927
|
Object.assign(mcpDeps, d.mcp);
|
|
663
928
|
}
|
|
664
929
|
if (Object.keys(mcpDeps).length > 0) {
|
|
665
|
-
const
|
|
666
|
-
const mcpJsonPath = resolve(cwd, ".claude", ".mcp.json");
|
|
930
|
+
const mcpJsonPath = resolve(projectDir, ".claude", ".mcp.json");
|
|
667
931
|
if (opts.dryRun) {
|
|
668
932
|
process.stderr.write(`Dry-run: would write ${Object.keys(mcpDeps).length} MCP server(s) to ${mcpJsonPath}\n`);
|
|
669
933
|
}
|
|
@@ -681,11 +945,19 @@ export async function runSync(opts) {
|
|
|
681
945
|
for (const [name, wfDef] of Object.entries(workflows)) {
|
|
682
946
|
const result = compileWorkflow(wfDef, { workflow_key: name });
|
|
683
947
|
if (result.ok) {
|
|
684
|
-
skillResults.push(compileWorkflowToSkill(result.plan, roles, ir, variables));
|
|
948
|
+
skillResults.push(compileWorkflowToSkill(result.plan, roles, ir, variables, { surface: skillSurface }));
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
if (skillSurface === "claude_code") {
|
|
952
|
+
for (const [name, controllerDef] of Object.entries(controllers)) {
|
|
953
|
+
const controllerKind = getControllerKindMetadata(controllerDef.kind);
|
|
954
|
+
if (controllerKind?.supportsSkillGeneration) {
|
|
955
|
+
skillResults.push(compileControllerToSkill(name, controllerDef, variables, workflows));
|
|
956
|
+
}
|
|
685
957
|
}
|
|
686
958
|
}
|
|
687
|
-
|
|
688
|
-
|
|
959
|
+
else if (Object.keys(controllers).length > 0) {
|
|
960
|
+
process.stderr.write("codex_cli unsupported: controller_skill_projection\n");
|
|
689
961
|
}
|
|
690
962
|
if (skillResults.length > 0) {
|
|
691
963
|
let written = skillResults.flatMap((result) => {
|
|
@@ -708,7 +980,13 @@ export async function runSync(opts) {
|
|
|
708
980
|
}
|
|
709
981
|
}
|
|
710
982
|
for (const file of written)
|
|
711
|
-
trackSyncOutput(trackedOutputs, "skills-controllers", file
|
|
983
|
+
trackSyncOutput(trackedOutputs, "skills-controllers", file, {
|
|
984
|
+
kind: "skill_controller",
|
|
985
|
+
target: skillSurface,
|
|
986
|
+
adapter: skillSurface,
|
|
987
|
+
ownership: "intentdna_owned",
|
|
988
|
+
trackedByLock: true,
|
|
989
|
+
});
|
|
712
990
|
}
|
|
713
991
|
}
|
|
714
992
|
}
|
|
@@ -717,7 +995,26 @@ export async function runSync(opts) {
|
|
|
717
995
|
const gateCount = ir.pre_execution_gates.length;
|
|
718
996
|
const filterCount = ir.tool_filters.length;
|
|
719
997
|
process.stderr.write(` ${directiveCount} directives, ${gateCount} gates, ${filterCount} filters\n`);
|
|
720
|
-
// Step 9: Generate
|
|
998
|
+
// Step 9: Generate Codex CLI adapter artifacts
|
|
999
|
+
if (opts.codex) {
|
|
1000
|
+
const packageVersion = await getPackageVersion() ?? "0.1.0";
|
|
1001
|
+
const codexPlan = createCodexSyncTargetPlan(ir, {
|
|
1002
|
+
hookInstallMode: opts.codexInstallMode ?? "project_fallback",
|
|
1003
|
+
packageVersion,
|
|
1004
|
+
});
|
|
1005
|
+
if (opts.dryRun) {
|
|
1006
|
+
process.stderr.write(`Dry-run: would write Codex CLI adapter artifacts (${codexPlan.hookInstallMode})\n`);
|
|
1007
|
+
process.stderr.write(formatCodexSyncTargetPlanSummary(codexPlan));
|
|
1008
|
+
}
|
|
1009
|
+
else {
|
|
1010
|
+
const written = await executeCodexSyncTargetPlan(codexPlan, { projectDir, dogfood: opts.codexDogfood });
|
|
1011
|
+
process.stderr.write(`Generated ${written.length} Codex CLI adapter artifact(s)\n`);
|
|
1012
|
+
for (const f of written)
|
|
1013
|
+
process.stderr.write(` ${f}\n`);
|
|
1014
|
+
}
|
|
1015
|
+
trackSyncTargetPlanOutputs(trackedOutputs, codexPlan, projectDir);
|
|
1016
|
+
}
|
|
1017
|
+
// Step 10: Generate .dna/lock file (only when --lock is specified or outputs exist)
|
|
721
1018
|
if (opts.lockFile && trackedOutputs.length > 0) {
|
|
722
1019
|
const lockPath = resolve(opts.lockFile);
|
|
723
1020
|
if (opts.dryRun) {
|
|
@@ -727,11 +1024,35 @@ export async function runSync(opts) {
|
|
|
727
1024
|
await generateLockFile(lockPath, opts.files, lockTrackedOutputPaths(trackedOutputs), ir.provenance, ir.compiled_ir_hash);
|
|
728
1025
|
process.stderr.write(`Lock file: ${lockPath}\n`);
|
|
729
1026
|
}
|
|
730
|
-
trackSyncOutput(trackedOutputs, "lock-file", lockPath
|
|
1027
|
+
trackSyncOutput(trackedOutputs, "lock-file", lockPath, {
|
|
1028
|
+
kind: "sync_lock_file",
|
|
1029
|
+
target: "generic",
|
|
1030
|
+
ownership: "diagnostic",
|
|
1031
|
+
trackedByLock: false,
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
let registryArtifacts = 0;
|
|
1035
|
+
if (!opts.dryRun && trackedOutputs.length > 0) {
|
|
1036
|
+
const packageVersion = await getPackageVersion() ?? undefined;
|
|
1037
|
+
const registry = await writeSyncArtifactRegistry({
|
|
1038
|
+
projectDir,
|
|
1039
|
+
dnaVersion: packageVersion,
|
|
1040
|
+
template: {
|
|
1041
|
+
sourceDnaIds: ir.source_dna_ids,
|
|
1042
|
+
configFiles: expandedFiles.map((file) => resolve(file)),
|
|
1043
|
+
compiledIrHash: ir.compiled_ir_hash,
|
|
1044
|
+
},
|
|
1045
|
+
artifacts: trackedOutputs,
|
|
1046
|
+
});
|
|
1047
|
+
registryArtifacts = registry.artifacts.length;
|
|
1048
|
+
process.stderr.write(`Sync artifact registry: ${resolve(projectDir, ".dna", "sync", "registry.json")} (${registry.artifacts.length} artifact(s))\n`);
|
|
731
1049
|
}
|
|
732
1050
|
if (opts.dryRun || opts.postSummary) {
|
|
733
1051
|
summarizeSyncOutputs(trackedOutputs, opts.dryRun === true);
|
|
734
1052
|
}
|
|
1053
|
+
if (opts.json) {
|
|
1054
|
+
process.stdout.write(JSON.stringify(buildSyncRunJsonResult(opts, projectDir, expandedFiles.map((file) => resolve(file)), trackedOutputs, registryArtifacts), null, 2) + "\n");
|
|
1055
|
+
}
|
|
735
1056
|
return 0;
|
|
736
1057
|
}
|
|
737
1058
|
catch (err) {
|
|
@@ -787,29 +1108,3 @@ async function writeMCPConfig(mcpDeps, mcpJsonPath, variables) {
|
|
|
787
1108
|
function substituteVariables(template, vars) {
|
|
788
1109
|
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? `{{${key}}}`);
|
|
789
1110
|
}
|
|
790
|
-
/**
|
|
791
|
-
* G2/G3: Register the built-in DNA MCP server (dna-mcp) in .claude/.mcp.json.
|
|
792
|
-
* Idempotent: only adds if not already present.
|
|
793
|
-
*/
|
|
794
|
-
async function registerDNAMCPServer(cwd) {
|
|
795
|
-
const mcpJsonPath = resolve(cwd, ".claude", ".mcp.json");
|
|
796
|
-
let existing = {};
|
|
797
|
-
try {
|
|
798
|
-
const raw = await readFile(mcpJsonPath, "utf-8");
|
|
799
|
-
existing = JSON.parse(raw);
|
|
800
|
-
}
|
|
801
|
-
catch { /* doesn't exist yet */ }
|
|
802
|
-
const mcpServers = (existing.mcpServers ?? {});
|
|
803
|
-
// Don't overwrite if user already configured it
|
|
804
|
-
if (mcpServers["intentdna"])
|
|
805
|
-
return mcpJsonPath;
|
|
806
|
-
mcpServers["intentdna"] = {
|
|
807
|
-
command: "dna-mcp",
|
|
808
|
-
args: ["--project-dir", cwd],
|
|
809
|
-
};
|
|
810
|
-
existing.mcpServers = mcpServers;
|
|
811
|
-
await mkdir(dirname(mcpJsonPath), { recursive: true });
|
|
812
|
-
await writeFileAsync(mcpJsonPath, JSON.stringify(existing, null, 2) + "\n", "utf-8");
|
|
813
|
-
process.stderr.write(`MCP: registered dna-mcp server in ${mcpJsonPath}\n`);
|
|
814
|
-
return mcpJsonPath;
|
|
815
|
-
}
|