@kontextmind/kxm 0.6.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 +19 -0
- package/.kxm/README.md +14 -0
- package/.kxm/assets/README.md +5 -0
- package/.kxm/assets/retrospectives/README.md +5 -0
- package/.kxm/config/README.md +5 -0
- package/.kxm/config/agents.json +43 -0
- package/.kxm/config/env.example +56 -0
- package/.kxm/config/update.example.yaml +9 -0
- package/.kxm/config/workflows/fix.json +160 -0
- package/.kxm/config/workflows/jira-development.json +116 -0
- package/.kxm/config/workflows/provenance-quorum.json +150 -0
- package/.kxm/config/workflows/v04-dogfood.json +72 -0
- package/CHANGELOG.md +465 -0
- package/LICENSE +21 -0
- package/README.md +306 -0
- package/SECURITY.md +72 -0
- package/docs/README.md +48 -0
- package/docs/agent-communication-envelopes-and-gates.md +553 -0
- package/docs/architecture.md +242 -0
- package/docs/assignment-runner.md +241 -0
- package/docs/configuration.md +361 -0
- package/docs/continuous-improvement.md +114 -0
- package/docs/getting-started.md +253 -0
- package/docs/kxm-handbook.md +1090 -0
- package/docs/operations.md +205 -0
- package/docs/provenance-gates.md +291 -0
- package/docs/skills.md +45 -0
- package/docs/templates/README.md +95 -0
- package/docs/templates/adr.md +88 -0
- package/docs/templates/architecture.md +120 -0
- package/docs/templates/bug-fix.md +109 -0
- package/docs/templates/feature.md +108 -0
- package/docs/templates/handoff.md +72 -0
- package/docs/templates/postmortem.md +77 -0
- package/docs/templates/research.md +100 -0
- package/docs/templates/review.md +85 -0
- package/docs/templates/runbook.md +73 -0
- package/docs/templates/test-plan.md +87 -0
- package/docs/templates/test-report.md +72 -0
- package/docs/test-matrix.md +121 -0
- package/docs/troubleshooting.md +249 -0
- package/docs/vnext/README.md +62 -0
- package/docs/vnext/architecture.md +185 -0
- package/docs/vnext/effects-and-recovery.md +172 -0
- package/docs/vnext/lifecycles.md +235 -0
- package/docs/vnext/migration.md +220 -0
- package/docs/vnext/routing.md +184 -0
- package/docs/vnext/synchronization.md +172 -0
- package/docs/vnext/terminology.md +240 -0
- package/docs/vnext/validation.md +335 -0
- package/docs/webhook-workflows.md +240 -0
- package/docs/workflow-guide.md +1150 -0
- package/examples/README.md +102 -0
- package/examples/provenance-workflow.json +40 -0
- package/examples/requester.ts +30 -0
- package/examples/reviewer-agent.ts +29 -0
- package/examples/roundtrip.ts +46 -0
- package/examples/vnext/.kxm/agents/coordinator.yaml +16 -0
- package/examples/vnext/.kxm/agents/critic-1.yaml +16 -0
- package/examples/vnext/.kxm/agents/critic-2.yaml +15 -0
- package/examples/vnext/.kxm/agents/critic-3.yaml +15 -0
- package/examples/vnext/.kxm/agents/implementer.yaml +15 -0
- package/examples/vnext/.kxm/agents/planner.yaml +13 -0
- package/examples/vnext/.kxm/agents/reproducer.yaml +15 -0
- package/examples/vnext/.kxm/agents/reviewer.yaml +15 -0
- package/examples/vnext/.kxm/gates.yaml +8 -0
- package/examples/vnext/.kxm/models/critic-claude.yaml +11 -0
- package/examples/vnext/.kxm/models/critic-gemini.yaml +11 -0
- package/examples/vnext/.kxm/models/critic-grok.yaml +12 -0
- package/examples/vnext/.kxm/models/implementation.yaml +14 -0
- package/examples/vnext/.kxm/models/primary.yaml +17 -0
- package/examples/vnext/.kxm/prices.yaml +111 -0
- package/examples/vnext/.kxm/project/env.yaml +7 -0
- package/examples/vnext/.kxm/project.yaml +32 -0
- package/examples/vnext/.kxm/repo/repo.yaml +8 -0
- package/examples/vnext/.kxm/workflows/default.yaml +92 -0
- package/examples/vnext/.kxm/workflows/fix.yaml +376 -0
- package/examples/vnext/.kxm/workflows/improve.yaml +57 -0
- package/examples/vnext/README.md +53 -0
- package/examples/vnext/records/assignment-result-recorded.json +63 -0
- package/examples/vnext/records/assignment-result.json +46 -0
- package/examples/vnext/records/context-candidate.json +42 -0
- package/examples/vnext/records/delivery-manifest.json +66 -0
- package/examples/vnext/records/effect-uncertainty-resolved-sync.json +67 -0
- package/examples/vnext/records/effect-uncertainty-resolved.json +62 -0
- package/examples/vnext/records/run-created.json +54 -0
- package/examples/vnext/records/sync-event.json +65 -0
- package/examples/vnext/repositories/api/.kxm/repo/env.yaml +7 -0
- package/examples/vnext/repositories/api/.kxm/repo/repo.yaml +8 -0
- package/examples/vnext/repositories/web/.kxm/repo/repo.yaml +8 -0
- package/examples/workflow-signal.ts +63 -0
- package/package.json +129 -0
- package/plugins/kxm/.claude-plugin/plugin.json +73 -0
- package/plugins/kxm/.mcp.json +19 -0
- package/plugins/kxm/README.md +93 -0
- package/plugins/kxm/dist/cli.js +42853 -0
- package/plugins/kxm/dist/client.js +416 -0
- package/plugins/kxm/dist/core.js +1823 -0
- package/plugins/kxm/dist/extension.js +3797 -0
- package/plugins/kxm/dist/mcp-server.js +17104 -0
- package/plugins/kxm/dist/runtime.js +23361 -0
- package/plugins/kxm/dist/server.js +13640 -0
- package/plugins/kxm/dist/vnext-runtime-supervisor.js +21109 -0
- package/plugins/kxm/package.json +12 -0
- package/plugins/kxm/skills/kxm/SKILL.md +97 -0
- package/plugins/kxm/skills/kxm/references/protocol.md +103 -0
- package/plugins/kxm/skills/kxm-session/SKILL.md +53 -0
- package/plugins/kxm/src/arbiter.ts +355 -0
- package/plugins/kxm/src/artifacts-exist.ts +62 -0
- package/plugins/kxm/src/autocomplete.ts +236 -0
- package/plugins/kxm/src/cli.ts +3707 -0
- package/plugins/kxm/src/client.ts +614 -0
- package/plugins/kxm/src/commands.ts +1063 -0
- package/plugins/kxm/src/config.ts +290 -0
- package/plugins/kxm/src/context/providers.ts +101 -0
- package/plugins/kxm/src/context-packet.ts +332 -0
- package/plugins/kxm/src/context.ts +499 -0
- package/plugins/kxm/src/core.ts +6 -0
- package/plugins/kxm/src/database.ts +563 -0
- package/plugins/kxm/src/diagnostics.ts +184 -0
- package/plugins/kxm/src/envelope.ts +118 -0
- package/plugins/kxm/src/extension.ts +895 -0
- package/plugins/kxm/src/external-effects.ts +299 -0
- package/plugins/kxm/src/github-watch.ts +255 -0
- package/plugins/kxm/src/hub-binding.ts +160 -0
- package/plugins/kxm/src/hub.ts +2502 -0
- package/plugins/kxm/src/improve.ts +383 -0
- package/plugins/kxm/src/inbox.ts +10 -0
- package/plugins/kxm/src/kxm-install-kind.ts +113 -0
- package/plugins/kxm/src/kxm-update-config.ts +39 -0
- package/plugins/kxm/src/kxm-update.ts +238 -0
- package/plugins/kxm/src/local-snapshot.ts +406 -0
- package/plugins/kxm/src/logger.ts +198 -0
- package/plugins/kxm/src/mcp-server.ts +143 -0
- package/plugins/kxm/src/memory.ts +385 -0
- package/plugins/kxm/src/nous-pi.ts +287 -0
- package/plugins/kxm/src/nous-provider.ts +729 -0
- package/plugins/kxm/src/price-calc.ts +87 -0
- package/plugins/kxm/src/prices.ts +121 -0
- package/plugins/kxm/src/protocol.ts +172 -0
- package/plugins/kxm/src/recovery.ts +211 -0
- package/plugins/kxm/src/redact.ts +26 -0
- package/plugins/kxm/src/retrospective.ts +400 -0
- package/plugins/kxm/src/routing.ts +830 -0
- package/plugins/kxm/src/runtime.ts +9 -0
- package/plugins/kxm/src/server.ts +117 -0
- package/plugins/kxm/src/session-work.ts +571 -0
- package/plugins/kxm/src/session.ts +184 -0
- package/plugins/kxm/src/skills.ts +535 -0
- package/plugins/kxm/src/state.ts +326 -0
- package/plugins/kxm/src/store.ts +637 -0
- package/plugins/kxm/src/studio-layout.ts +268 -0
- package/plugins/kxm/src/suggest.ts +162 -0
- package/plugins/kxm/src/task-manager.ts +244 -0
- package/plugins/kxm/src/telemetry.ts +116 -0
- package/plugins/kxm/src/tui.ts +1046 -0
- package/plugins/kxm/src/vnext-bindings.ts +403 -0
- package/plugins/kxm/src/vnext-config.ts +1646 -0
- package/plugins/kxm/src/vnext-engine-artifacts.ts +86 -0
- package/plugins/kxm/src/vnext-engine-command.ts +533 -0
- package/plugins/kxm/src/vnext-engine-compile.ts +722 -0
- package/plugins/kxm/src/vnext-engine-evidence.ts +273 -0
- package/plugins/kxm/src/vnext-engine-fold.ts +1400 -0
- package/plugins/kxm/src/vnext-engine-gate-records.ts +583 -0
- package/plugins/kxm/src/vnext-engine-plan.ts +717 -0
- package/plugins/kxm/src/vnext-engine.ts +2458 -0
- package/plugins/kxm/src/vnext-gate-hash.ts +10 -0
- package/plugins/kxm/src/vnext-harness.ts +1142 -0
- package/plugins/kxm/src/vnext-init.ts +430 -0
- package/plugins/kxm/src/vnext-migrate.ts +1848 -0
- package/plugins/kxm/src/vnext-oneshot-producer.ts +424 -0
- package/plugins/kxm/src/vnext-permission.ts +936 -0
- package/plugins/kxm/src/vnext-pi-producer.ts +628 -0
- package/plugins/kxm/src/vnext-repair.ts +1094 -0
- package/plugins/kxm/src/vnext-runtime-owner.ts +320 -0
- package/plugins/kxm/src/vnext-runtime-store.ts +1560 -0
- package/plugins/kxm/src/vnext-runtime-supervisor.ts +586 -0
- package/plugins/kxm/src/vnext-runtime.ts +663 -0
- package/plugins/kxm/src/vnext-template.ts +247 -0
- package/plugins/kxm/src/wiki.ts +313 -0
- package/plugins/kxm/src/workflow.ts +1548 -0
- package/schemas/vnext/README.md +46 -0
- package/schemas/vnext/agent.schema.json +40 -0
- package/schemas/vnext/assignment-result.schema.json +66 -0
- package/schemas/vnext/backup-manifest.schema.json +89 -0
- package/schemas/vnext/candidate.schema.json +109 -0
- package/schemas/vnext/common.schema.json +422 -0
- package/schemas/vnext/context-candidate.schema.json +76 -0
- package/schemas/vnext/context-packet.schema.json +192 -0
- package/schemas/vnext/delivery-manifest.schema.json +159 -0
- package/schemas/vnext/environment.schema.json +66 -0
- package/schemas/vnext/gate-registry.schema.json +109 -0
- package/schemas/vnext/handoff-manifest.schema.json +146 -0
- package/schemas/vnext/init-operation.schema.json +61 -0
- package/schemas/vnext/local-repository-bindings.schema.json +30 -0
- package/schemas/vnext/memory-record.schema.json +45 -0
- package/schemas/vnext/migration-decision.schema.json +26 -0
- package/schemas/vnext/migration-plan.schema.json +123 -0
- package/schemas/vnext/migration-receipt.schema.json +52 -0
- package/schemas/vnext/model.schema.json +42 -0
- package/schemas/vnext/permission-diff.schema.json +57 -0
- package/schemas/vnext/prices.schema.json +115 -0
- package/schemas/vnext/project.schema.json +85 -0
- package/schemas/vnext/repository.schema.json +24 -0
- package/schemas/vnext/run-event.schema.json +460 -0
- package/schemas/vnext/session-brief.schema.json +153 -0
- package/schemas/vnext/sync-event.schema.json +234 -0
- package/schemas/vnext/template-provenance.schema.json +38 -0
- package/schemas/vnext/workflow.schema.json +248 -0
- package/scripts/assignment-run.d.mts +354 -0
- package/scripts/assignment-run.mjs +4451 -0
- package/scripts/build-runtime.mjs +56 -0
- package/scripts/check-generated.mjs +77 -0
- package/scripts/check-versions.mjs +34 -0
- package/scripts/emit-codex-artifacts.d.mts +9 -0
- package/scripts/emit-codex-artifacts.mjs +91 -0
- package/scripts/harness-run.d.mts +83 -0
- package/scripts/harness-run.mjs +2095 -0
- package/scripts/kxm-hub.mjs +105 -0
- package/scripts/kxm-publish-npm.mjs +327 -0
- package/scripts/kxm-release-github.mjs +472 -0
- package/scripts/kxm-runtime-supervisor.mjs +7 -0
- package/scripts/kxm-worker.mjs +1127 -0
- package/scripts/kxm.mjs +27 -0
- package/scripts/roster-policy.d.mts +20 -0
- package/scripts/roster-policy.mjs +161 -0
- package/scripts/smoke-multi-pi.mjs +479 -0
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { basename, join } from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
VnextConfigError,
|
|
6
|
+
discoverGitRoot,
|
|
7
|
+
loadVnextProject,
|
|
8
|
+
assertNoRegisteredGates,
|
|
9
|
+
planVnextInitialization,
|
|
10
|
+
type JsonObject,
|
|
11
|
+
type VnextConfigIssue,
|
|
12
|
+
type VnextConfigOptions,
|
|
13
|
+
type VnextInitializationPlan,
|
|
14
|
+
} from "./vnext-config.ts";
|
|
15
|
+
import {
|
|
16
|
+
planVnextLocalBindings,
|
|
17
|
+
readVnextLocalBindings,
|
|
18
|
+
withVnextLocalBindingLock,
|
|
19
|
+
writeVnextLocalBindings,
|
|
20
|
+
type VnextLocalBindingLock,
|
|
21
|
+
type VnextLocalBindingStoreOptions,
|
|
22
|
+
} from "./vnext-bindings.ts";
|
|
23
|
+
import {
|
|
24
|
+
commitVnextInitTransaction,
|
|
25
|
+
hasVnextInitTransaction,
|
|
26
|
+
inspectVnextInitTransaction,
|
|
27
|
+
planVnextTemplateRepair,
|
|
28
|
+
prepareAndApplyVnextCreate,
|
|
29
|
+
prepareAndApplyVnextRepair,
|
|
30
|
+
resumeVnextInitTransaction,
|
|
31
|
+
type VnextRepairRuntimeOptions,
|
|
32
|
+
type VnextTemplateRepairPlan,
|
|
33
|
+
} from "./vnext-repair.ts";
|
|
34
|
+
import {
|
|
35
|
+
CURRENT_VNEXT_TEMPLATE_VARIANT,
|
|
36
|
+
renderVnextTemplate,
|
|
37
|
+
type VnextTemplateVariant,
|
|
38
|
+
} from "./vnext-template.ts";
|
|
39
|
+
|
|
40
|
+
export interface VnextInitOptions extends VnextConfigOptions {
|
|
41
|
+
projectId?: string;
|
|
42
|
+
projectName?: string;
|
|
43
|
+
dryRun?: boolean;
|
|
44
|
+
localStateRoot?: string;
|
|
45
|
+
/** Test/future-template injection; the CLI always uses the current built-in variant. */
|
|
46
|
+
templateVariant?: VnextTemplateVariant;
|
|
47
|
+
/** Fault injection used by crash-recovery tests. */
|
|
48
|
+
testFaultAt?: "prepared" | "first-resource" | "provenance" | "verified";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface VnextInitResult {
|
|
52
|
+
action: "planned" | "created" | "joined" | "repaired" | "resumed" | "validated";
|
|
53
|
+
plan: VnextInitializationPlan;
|
|
54
|
+
projectRoot?: string;
|
|
55
|
+
configRevision?: string;
|
|
56
|
+
localBindingFile?: string;
|
|
57
|
+
bindingsChanged?: boolean;
|
|
58
|
+
repairPlan?: VnextTemplateRepairPlan;
|
|
59
|
+
resumePending?: boolean;
|
|
60
|
+
transactionKind?: "create" | "repair";
|
|
61
|
+
files: readonly string[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const PROJECT_ID = /^prj_[A-Za-z0-9][A-Za-z0-9_-]{5,127}$/;
|
|
65
|
+
|
|
66
|
+
function initIssue(code: string, file: string, message: string): VnextConfigIssue {
|
|
67
|
+
return { phase: "discovery", code, file, message };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function normalizedProjectName(root: string, requested?: string): string {
|
|
71
|
+
const name = (requested ?? basename(root) ?? "KXM Project").trim();
|
|
72
|
+
if (name.length < 1 || name.length > 120 || /[\u0000\r\n]/.test(name)) {
|
|
73
|
+
throw new VnextConfigError([initIssue("project_name_invalid", ".kxm/project.yaml", "project name must contain 1-120 characters on one line")]);
|
|
74
|
+
}
|
|
75
|
+
return name;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function generatedProjectId(requested?: string): string {
|
|
79
|
+
const id = requested?.trim() || `prj_${randomUUID().replaceAll("-", "")}`;
|
|
80
|
+
if (!PROJECT_ID.test(id) || id.length > 144) {
|
|
81
|
+
throw new VnextConfigError([initIssue("project_id_invalid", ".kxm/project.yaml", "project ID must satisfy the kxm.project.v1 opaque ID grammar and use the prj_ prefix")]);
|
|
82
|
+
}
|
|
83
|
+
return id;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function configOptions(options: VnextInitOptions, repositoryBindings: Readonly<Record<string, string>>): VnextConfigOptions {
|
|
87
|
+
assertNoRegisteredGates(options);
|
|
88
|
+
return {
|
|
89
|
+
...(options.schemasDir === undefined ? {} : { schemasDir: options.schemasDir }),
|
|
90
|
+
repositoryBindings,
|
|
91
|
+
...(options.registeredExecutors === undefined ? {} : { registeredExecutors: options.registeredExecutors }),
|
|
92
|
+
...(options.registeredToolPresets === undefined ? {} : { registeredToolPresets: options.registeredToolPresets }),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function repairOptions(
|
|
97
|
+
options: VnextInitOptions,
|
|
98
|
+
repositoryBindings: Readonly<Record<string, string>>,
|
|
99
|
+
): VnextRepairRuntimeOptions {
|
|
100
|
+
return {
|
|
101
|
+
...configOptions(options, repositoryBindings),
|
|
102
|
+
templateVariant: options.templateVariant ?? CURRENT_VNEXT_TEMPLATE_VARIANT,
|
|
103
|
+
...(options.testFaultAt === undefined ? {} : { testFaultAt: options.testFaultAt }),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function bindingStoreOptions(options: VnextInitOptions): VnextLocalBindingStoreOptions {
|
|
108
|
+
return {
|
|
109
|
+
...(options.localStateRoot === undefined ? {} : { stateRoot: options.localStateRoot }),
|
|
110
|
+
...(options.schemasDir === undefined ? {} : { schemasDir: options.schemasDir }),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function memberRepositoryIds(project: JsonObject): Set<string> { const repositories = Array.isArray(project.repositories) ? project.repositories : [];
|
|
115
|
+
return new Set(repositories.flatMap((candidate) => {
|
|
116
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return [];
|
|
117
|
+
const record = candidate as JsonObject;
|
|
118
|
+
return record.role === "member" && typeof record.id === "string" ? [record.id] : [];
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function completedPlan(projectRoot: string, options: VnextConfigOptions): VnextInitializationPlan {
|
|
123
|
+
return planVnextInitialization(projectRoot, options);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function finalizeBindingUpdate(
|
|
127
|
+
bundle: ReturnType<typeof loadVnextProject>,
|
|
128
|
+
persisted: ReturnType<typeof readVnextLocalBindings>,
|
|
129
|
+
allBindings: Readonly<Record<string, string>>,
|
|
130
|
+
explicitBindings: Readonly<Record<string, string>>,
|
|
131
|
+
storeOptions: VnextLocalBindingStoreOptions,
|
|
132
|
+
mutationLock: VnextLocalBindingLock | undefined,
|
|
133
|
+
dryRun: boolean,
|
|
134
|
+
): { localBindingFile?: string; bindingsChanged?: boolean } {
|
|
135
|
+
const memberIds = validateBindingIdentities(bundle, persisted, explicitBindings);
|
|
136
|
+
if (Object.keys(explicitBindings).length === 0) {
|
|
137
|
+
return persisted ? { localBindingFile: persisted.file } : {};
|
|
138
|
+
}
|
|
139
|
+
const memberBindings = Object.fromEntries(Object.entries(allBindings)
|
|
140
|
+
.filter(([repositoryId]) => memberIds.has(repositoryId)));
|
|
141
|
+
const projectId = String(bundle.project.value.id);
|
|
142
|
+
const bindingPlan = planVnextLocalBindings(bundle.projectRoot, projectId, memberBindings, storeOptions);
|
|
143
|
+
if (dryRun) return { localBindingFile: bindingPlan.file, bindingsChanged: bindingPlan.written };
|
|
144
|
+
if (bindingPlan.written && !mutationLock) throw new Error("project mutation lock is required to persist repository bindings");
|
|
145
|
+
const result = bindingPlan.written
|
|
146
|
+
? writeVnextLocalBindings(bundle.projectRoot, projectId, memberBindings, storeOptions, mutationLock)
|
|
147
|
+
: bindingPlan;
|
|
148
|
+
return { localBindingFile: result.file, bindingsChanged: result.written };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function plannedRepairResult(
|
|
152
|
+
plan: VnextInitializationPlan,
|
|
153
|
+
projectRoot: string,
|
|
154
|
+
repairPlan?: VnextTemplateRepairPlan,
|
|
155
|
+
): VnextInitResult {
|
|
156
|
+
return {
|
|
157
|
+
action: "planned",
|
|
158
|
+
plan,
|
|
159
|
+
projectRoot,
|
|
160
|
+
...(repairPlan === undefined ? {} : { repairPlan }),
|
|
161
|
+
files: [],
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function validateBindingIdentities(
|
|
166
|
+
bundle: ReturnType<typeof loadVnextProject>,
|
|
167
|
+
persisted: ReturnType<typeof readVnextLocalBindings>,
|
|
168
|
+
explicit: Readonly<Record<string, string>>,
|
|
169
|
+
): Set<string> {
|
|
170
|
+
const projectId = String(bundle.project.value.id);
|
|
171
|
+
if (persisted && persisted.projectId !== projectId) {
|
|
172
|
+
throw new VnextConfigError([initIssue("local_binding_project_id_mismatch", "Runtime-local repository bindings", "binding record belongs to a different project identity")]);
|
|
173
|
+
}
|
|
174
|
+
const memberIds = memberRepositoryIds(bundle.project.value);
|
|
175
|
+
for (const repositoryId of Object.keys(persisted?.repositories ?? {})) {
|
|
176
|
+
if (!memberIds.has(repositoryId)) {
|
|
177
|
+
throw new VnextConfigError([initIssue("local_binding_repository_invalid", "Runtime-local repository bindings", `persisted binding ${repositoryId} is not a member repository`)]);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
for (const repositoryId of Object.keys(explicit)) {
|
|
181
|
+
if (!memberIds.has(repositoryId)) {
|
|
182
|
+
throw new VnextConfigError([initIssue("local_binding_repository_invalid", "Runtime-local repository bindings", `explicit binding ${repositoryId} is not a member repository`)]);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return memberIds;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Unified initialization: create, resume a pinned operation, apply a
|
|
190
|
+
* provenance-backed conflict-free template repair, join with local member
|
|
191
|
+
* bindings, or return a non-mutating migration/conflict plan.
|
|
192
|
+
*/
|
|
193
|
+
function initializeVnextProjectAtGitRoot(
|
|
194
|
+
start: string,
|
|
195
|
+
gitRoot: string,
|
|
196
|
+
options: VnextInitOptions,
|
|
197
|
+
mutationLock?: VnextLocalBindingLock,
|
|
198
|
+
): VnextInitResult {
|
|
199
|
+
const storeOptions = bindingStoreOptions(options);
|
|
200
|
+
const persisted = existsSync(join(gitRoot, ".kxm", "project.yaml"))
|
|
201
|
+
? readVnextLocalBindings(gitRoot, storeOptions)
|
|
202
|
+
: undefined;
|
|
203
|
+
const repositoryBindings = Object.fromEntries([
|
|
204
|
+
...Object.entries(persisted?.repositories ?? {}),
|
|
205
|
+
...Object.entries(options.repositoryBindings ?? {}),
|
|
206
|
+
]);
|
|
207
|
+
const loaderOptions = configOptions(options, repositoryBindings);
|
|
208
|
+
const transactionOptions = repairOptions(options, repositoryBindings);
|
|
209
|
+
|
|
210
|
+
if (hasVnextInitTransaction(gitRoot)) {
|
|
211
|
+
const operation = inspectVnextInitTransaction(gitRoot, options.schemasDir);
|
|
212
|
+
const plan = planVnextInitialization(start, loaderOptions);
|
|
213
|
+
if (!operation) {
|
|
214
|
+
if (options.dryRun) return { action: "planned", plan, projectRoot: gitRoot, resumePending: true, files: [] };
|
|
215
|
+
if (!mutationLock) throw new Error("project mutation lock is required to clean an empty transaction");
|
|
216
|
+
resumeVnextInitTransaction(gitRoot, transactionOptions);
|
|
217
|
+
} else if (options.dryRun) {
|
|
218
|
+
return {
|
|
219
|
+
action: "planned",
|
|
220
|
+
plan,
|
|
221
|
+
projectRoot: gitRoot,
|
|
222
|
+
resumePending: true,
|
|
223
|
+
transactionKind: operation.kind,
|
|
224
|
+
files: operation.files.map((file) => file.path),
|
|
225
|
+
};
|
|
226
|
+
} else {
|
|
227
|
+
if (!mutationLock) throw new Error("project mutation lock is required to resume initialization");
|
|
228
|
+
if (options.projectId !== undefined && options.projectId !== operation.projectId) {
|
|
229
|
+
throw new VnextConfigError([initIssue("resume_project_id_mismatch", "--project-id", "requested project ID differs from the pinned initialization transaction")]);
|
|
230
|
+
}
|
|
231
|
+
if (options.projectName !== undefined && options.projectName !== operation.projectName) {
|
|
232
|
+
throw new VnextConfigError([initIssue("resume_project_name_mismatch", "--name", "requested project name differs from the pinned initialization transaction")]);
|
|
233
|
+
}
|
|
234
|
+
if (operation.kind === "repair" && Object.keys(options.repositoryBindings ?? {}).length > 0) {
|
|
235
|
+
const currentBundle = loadVnextProject(gitRoot, loaderOptions);
|
|
236
|
+
finalizeBindingUpdate(
|
|
237
|
+
currentBundle,
|
|
238
|
+
persisted,
|
|
239
|
+
repositoryBindings,
|
|
240
|
+
options.repositoryBindings ?? {},
|
|
241
|
+
storeOptions,
|
|
242
|
+
mutationLock,
|
|
243
|
+
false,
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
const result = resumeVnextInitTransaction(gitRoot, transactionOptions);
|
|
247
|
+
if (!result) throw new Error("initialization transaction disappeared while holding the project lock");
|
|
248
|
+
const bindingResult = finalizeBindingUpdate(
|
|
249
|
+
result.bundle,
|
|
250
|
+
persisted,
|
|
251
|
+
repositoryBindings,
|
|
252
|
+
options.repositoryBindings ?? {},
|
|
253
|
+
storeOptions,
|
|
254
|
+
mutationLock,
|
|
255
|
+
false,
|
|
256
|
+
);
|
|
257
|
+
commitVnextInitTransaction(gitRoot, options.schemasDir);
|
|
258
|
+
return {
|
|
259
|
+
action: "resumed",
|
|
260
|
+
plan: completedPlan(gitRoot, loaderOptions),
|
|
261
|
+
projectRoot: gitRoot,
|
|
262
|
+
configRevision: result.bundle.configRevision,
|
|
263
|
+
transactionKind: result.kind,
|
|
264
|
+
...bindingResult,
|
|
265
|
+
files: result.files,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const plan = planVnextInitialization(start, loaderOptions);
|
|
271
|
+
if (plan.mode === "migrate") {
|
|
272
|
+
return { action: "planned", plan, ...(plan.projectRoot ? { projectRoot: plan.projectRoot } : {}), files: [] };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (plan.mode === "repair") {
|
|
276
|
+
const projectRoot = plan.projectRoot ?? gitRoot;
|
|
277
|
+
let templateRepair: VnextTemplateRepairPlan | undefined;
|
|
278
|
+
try {
|
|
279
|
+
templateRepair = planVnextTemplateRepair(projectRoot, transactionOptions);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
if (!(error instanceof VnextConfigError)) throw error;
|
|
282
|
+
return plannedRepairResult({ ...plan, issues: [...plan.issues, ...error.issues] }, projectRoot);
|
|
283
|
+
}
|
|
284
|
+
if (!templateRepair?.canApply) return plannedRepairResult(plan, projectRoot, templateRepair);
|
|
285
|
+
if (options.dryRun) return plannedRepairResult(plan, projectRoot, templateRepair);
|
|
286
|
+
if (Object.keys(options.repositoryBindings ?? {}).length > 0) {
|
|
287
|
+
const blockedRepair: VnextTemplateRepairPlan = {
|
|
288
|
+
...templateRepair,
|
|
289
|
+
canApply: false,
|
|
290
|
+
issues: [...templateRepair.issues, initIssue(
|
|
291
|
+
"repair_binding_requires_ready_project",
|
|
292
|
+
"--repository",
|
|
293
|
+
"a first-time binding update cannot be combined with repair of an invalid project; establish a valid project or existing binding first",
|
|
294
|
+
)],
|
|
295
|
+
};
|
|
296
|
+
return plannedRepairResult(plan, projectRoot, blockedRepair);
|
|
297
|
+
}
|
|
298
|
+
if (!mutationLock) throw new Error("project mutation lock is required to apply template repair");
|
|
299
|
+
const repaired = prepareAndApplyVnextRepair(templateRepair, transactionOptions);
|
|
300
|
+
const bindingResult = finalizeBindingUpdate(
|
|
301
|
+
repaired.bundle,
|
|
302
|
+
persisted,
|
|
303
|
+
repositoryBindings,
|
|
304
|
+
options.repositoryBindings ?? {},
|
|
305
|
+
storeOptions,
|
|
306
|
+
mutationLock,
|
|
307
|
+
false,
|
|
308
|
+
);
|
|
309
|
+
commitVnextInitTransaction(projectRoot, options.schemasDir);
|
|
310
|
+
return {
|
|
311
|
+
action: "repaired",
|
|
312
|
+
plan: completedPlan(projectRoot, loaderOptions),
|
|
313
|
+
projectRoot,
|
|
314
|
+
configRevision: repaired.bundle.configRevision,
|
|
315
|
+
repairPlan: templateRepair,
|
|
316
|
+
...bindingResult,
|
|
317
|
+
files: repaired.files,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (plan.mode === "ready") {
|
|
322
|
+
const projectRoot = plan.projectRoot ?? gitRoot;
|
|
323
|
+
const bundle = loadVnextProject(projectRoot, loaderOptions);
|
|
324
|
+
const templateRepair = planVnextTemplateRepair(projectRoot, transactionOptions);
|
|
325
|
+
if (templateRepair?.changesRequired) {
|
|
326
|
+
if (!templateRepair.canApply || options.dryRun) return plannedRepairResult(plan, projectRoot, templateRepair);
|
|
327
|
+
if (!mutationLock) throw new Error("project mutation lock is required to apply template repair");
|
|
328
|
+
const bindingResult = finalizeBindingUpdate(
|
|
329
|
+
bundle,
|
|
330
|
+
persisted,
|
|
331
|
+
repositoryBindings,
|
|
332
|
+
options.repositoryBindings ?? {},
|
|
333
|
+
storeOptions,
|
|
334
|
+
mutationLock,
|
|
335
|
+
false,
|
|
336
|
+
);
|
|
337
|
+
const repaired = prepareAndApplyVnextRepair(templateRepair, transactionOptions);
|
|
338
|
+
finalizeBindingUpdate(
|
|
339
|
+
repaired.bundle,
|
|
340
|
+
persisted,
|
|
341
|
+
repositoryBindings,
|
|
342
|
+
options.repositoryBindings ?? {},
|
|
343
|
+
storeOptions,
|
|
344
|
+
mutationLock,
|
|
345
|
+
false,
|
|
346
|
+
);
|
|
347
|
+
commitVnextInitTransaction(projectRoot, options.schemasDir);
|
|
348
|
+
return {
|
|
349
|
+
action: "repaired",
|
|
350
|
+
plan: completedPlan(projectRoot, loaderOptions),
|
|
351
|
+
projectRoot,
|
|
352
|
+
configRevision: repaired.bundle.configRevision,
|
|
353
|
+
repairPlan: templateRepair,
|
|
354
|
+
...bindingResult,
|
|
355
|
+
files: repaired.files,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const bindingResult = finalizeBindingUpdate(
|
|
360
|
+
bundle,
|
|
361
|
+
persisted,
|
|
362
|
+
repositoryBindings,
|
|
363
|
+
options.repositoryBindings ?? {},
|
|
364
|
+
storeOptions,
|
|
365
|
+
mutationLock,
|
|
366
|
+
options.dryRun === true,
|
|
367
|
+
);
|
|
368
|
+
if (options.dryRun) {
|
|
369
|
+
return {
|
|
370
|
+
action: "planned",
|
|
371
|
+
plan,
|
|
372
|
+
projectRoot,
|
|
373
|
+
configRevision: bundle.configRevision,
|
|
374
|
+
...bindingResult,
|
|
375
|
+
files: [],
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
return {
|
|
379
|
+
action: bindingResult.bindingsChanged ? "joined" : "validated",
|
|
380
|
+
plan,
|
|
381
|
+
projectRoot,
|
|
382
|
+
configRevision: bundle.configRevision,
|
|
383
|
+
...bindingResult,
|
|
384
|
+
files: [],
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (plan.projectRoot !== gitRoot) {
|
|
389
|
+
throw new VnextConfigError([initIssue("git_root_required", ".", "kxm init must run inside the authoritative Git worktree")]);
|
|
390
|
+
}
|
|
391
|
+
const projectId = generatedProjectId(options.projectId);
|
|
392
|
+
const projectName = normalizedProjectName(gitRoot, options.projectName);
|
|
393
|
+
const rendered = renderVnextTemplate(projectId, projectName, options.templateVariant ?? CURRENT_VNEXT_TEMPLATE_VARIANT);
|
|
394
|
+
const files = [...rendered.files.keys()];
|
|
395
|
+
if (options.dryRun) return { action: "planned", plan, projectRoot: gitRoot, files };
|
|
396
|
+
if (!mutationLock) throw new Error("project mutation lock is required to create configuration");
|
|
397
|
+
if (existsSync(join(gitRoot, ".kxm"))) {
|
|
398
|
+
throw new VnextConfigError([initIssue("workspace_changed", ".kxm", "workspace changed after planning; existing .kxm state was not overwritten")]);
|
|
399
|
+
}
|
|
400
|
+
const created = prepareAndApplyVnextCreate(gitRoot, rendered, transactionOptions);
|
|
401
|
+
commitVnextInitTransaction(gitRoot, options.schemasDir);
|
|
402
|
+
return {
|
|
403
|
+
action: "created",
|
|
404
|
+
plan: completedPlan(gitRoot, loaderOptions),
|
|
405
|
+
projectRoot: gitRoot,
|
|
406
|
+
configRevision: created.bundle.configRevision,
|
|
407
|
+
files: created.files,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export function initializeVnextProject(start = process.cwd(), options: VnextInitOptions = {}): VnextInitResult {
|
|
412
|
+
assertNoRegisteredGates(options);
|
|
413
|
+
const gitRoot = discoverGitRoot(start);
|
|
414
|
+
if (!gitRoot) {
|
|
415
|
+
throw new VnextConfigError([initIssue("git_root_required", ".", "kxm init must run inside the authoritative Git worktree")]);
|
|
416
|
+
}
|
|
417
|
+
if (options.projectId !== undefined) generatedProjectId(options.projectId);
|
|
418
|
+
if (options.projectName !== undefined) normalizedProjectName(gitRoot, options.projectName);
|
|
419
|
+
if (options.dryRun) return initializeVnextProjectAtGitRoot(start, gitRoot, options);
|
|
420
|
+
|
|
421
|
+
const preflight = initializeVnextProjectAtGitRoot(start, gitRoot, { ...options, dryRun: true });
|
|
422
|
+
const mutationRequired = preflight.resumePending === true
|
|
423
|
+
|| preflight.plan.mode === "create"
|
|
424
|
+
|| (preflight.repairPlan?.canApply === true
|
|
425
|
+
&& !(preflight.plan.mode === "repair" && Object.keys(options.repositoryBindings ?? {}).length > 0))
|
|
426
|
+
|| (Object.keys(options.repositoryBindings ?? {}).length > 0 && preflight.plan.mode === "ready");
|
|
427
|
+
if (!mutationRequired) return initializeVnextProjectAtGitRoot(start, gitRoot, options);
|
|
428
|
+
const storeOptions = bindingStoreOptions(options);
|
|
429
|
+
return withVnextLocalBindingLock(gitRoot, storeOptions, (lock) => initializeVnextProjectAtGitRoot(start, gitRoot, options, lock));
|
|
430
|
+
}
|