@wairon/cli 5.0.2-dev.13 → 5.0.2-dev.15
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/dist/cli/index.js +1239 -716
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +286 -76
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -65,7 +65,7 @@ var init_defaults = __esm({
|
|
|
65
65
|
copilot: ".github/prompts",
|
|
66
66
|
codex: ".codex/agents"
|
|
67
67
|
};
|
|
68
|
-
WAIRON_VERSION = "5.0.2-dev.
|
|
68
|
+
WAIRON_VERSION = "5.0.2-dev.15";
|
|
69
69
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
70
70
|
ARCHITECT_AGENT_ID = "agent-architect";
|
|
71
71
|
ARCHITECT_TEMPLATE_ID = "architect";
|
|
@@ -458,7 +458,7 @@ var init_domain = __esm({
|
|
|
458
458
|
});
|
|
459
459
|
|
|
460
460
|
// src/models/project.ts
|
|
461
|
-
var import_zod3, BuiltinTargetConfigSchema, CustomTargetConfigSchema, TargetConfigSchema, NamingRuleConfigSchema, DocumentationRuleConfigSchema, ComplexityRuleConfigSchema, DesignDepthSchema, RulesConfigSchema, PathsConfigSchema, ProjectConfigSchema;
|
|
461
|
+
var import_zod3, BuiltinTargetConfigSchema, CustomTargetConfigSchema, TargetConfigSchema, NamingRuleConfigSchema, DocumentationRuleConfigSchema, ComplexityRuleConfigSchema, DesignDepthSchema, RulesConfigSchema, PathsConfigSchema, ProfileSelectionSubjectSchema, ProjectProfileSelectionSchema, ProjectConfigSchema;
|
|
462
462
|
var init_project = __esm({
|
|
463
463
|
"src/models/project.ts"() {
|
|
464
464
|
"use strict";
|
|
@@ -582,6 +582,24 @@ var init_project = __esm({
|
|
|
582
582
|
/** Base directory containing SDD specification files, relative to project root */
|
|
583
583
|
specsDir: import_zod3.z.string().default(".wai/specs")
|
|
584
584
|
});
|
|
585
|
+
ProfileSelectionSubjectSchema = import_zod3.z.object({
|
|
586
|
+
userId: import_zod3.z.string(),
|
|
587
|
+
kind: import_zod3.z.string(),
|
|
588
|
+
issuer: import_zod3.z.string(),
|
|
589
|
+
externalSubject: import_zod3.z.string().optional(),
|
|
590
|
+
displayName: import_zod3.z.string().optional(),
|
|
591
|
+
email: import_zod3.z.string().optional()
|
|
592
|
+
});
|
|
593
|
+
ProjectProfileSelectionSchema = import_zod3.z.object({
|
|
594
|
+
/** Selected architectural profile ids. The first resolvable one is applied as projectType. */
|
|
595
|
+
profileIds: import_zod3.z.array(import_zod3.z.string()).default([]),
|
|
596
|
+
/** Pack names the governing policy requires for this project. */
|
|
597
|
+
requiredPackNames: import_zod3.z.array(import_zod3.z.string()).default([]),
|
|
598
|
+
/** Pack names applied by default unless explicitly overridden. */
|
|
599
|
+
defaultPackNames: import_zod3.z.array(import_zod3.z.string()).optional(),
|
|
600
|
+
selectedBy: ProfileSelectionSubjectSchema.optional(),
|
|
601
|
+
selectedAt: import_zod3.z.string()
|
|
602
|
+
});
|
|
585
603
|
ProjectConfigSchema = import_zod3.z.object({
|
|
586
604
|
/**
|
|
587
605
|
* Schema version — used to detect incompatible config formats in future
|
|
@@ -622,6 +640,13 @@ var init_project = __esm({
|
|
|
622
640
|
useGlobalPacks: import_zod3.z.boolean().default(true)
|
|
623
641
|
}).optional(),
|
|
624
642
|
paths: PathsConfigSchema.default({}),
|
|
643
|
+
/**
|
|
644
|
+
* The profile/pack selection a hosted policy workflow applied to this project.
|
|
645
|
+
* The RECORD of what was chosen; `projectType` above is what actually governs
|
|
646
|
+
* validation. Modeled so the parse/write round trip preserves it (see
|
|
647
|
+
* ProjectProfileSelectionSchema).
|
|
648
|
+
*/
|
|
649
|
+
profileSelection: ProjectProfileSelectionSchema.optional(),
|
|
625
650
|
/**
|
|
626
651
|
* Path to a directory containing org/user-level default templates.
|
|
627
652
|
* Resolved before built-in templates but after project-local templates.
|
|
@@ -1914,6 +1939,47 @@ var init_loader = __esm({
|
|
|
1914
1939
|
}
|
|
1915
1940
|
});
|
|
1916
1941
|
|
|
1942
|
+
// src/core/statehash.ts
|
|
1943
|
+
function computeStateId() {
|
|
1944
|
+
const tree = {
|
|
1945
|
+
system: loadSystemSpec(),
|
|
1946
|
+
subsystems: loadSubsystemSpecs(),
|
|
1947
|
+
components: loadComponentSpecs(),
|
|
1948
|
+
interfaces: loadInterfaceSpecs(),
|
|
1949
|
+
implementations: loadImplementationSpecs(),
|
|
1950
|
+
types: loadTypeSpecs()
|
|
1951
|
+
};
|
|
1952
|
+
const digest = crypto.createHash("sha256").update(canonicalize(tree)).digest("hex");
|
|
1953
|
+
return { algorithm: "sha256", digest };
|
|
1954
|
+
}
|
|
1955
|
+
function stateIdEquals(a, b) {
|
|
1956
|
+
return !!a && !!b && a.algorithm === b.algorithm && a.digest === b.digest;
|
|
1957
|
+
}
|
|
1958
|
+
function canonicalize(value) {
|
|
1959
|
+
return JSON.stringify(sortKeys(value));
|
|
1960
|
+
}
|
|
1961
|
+
function sortKeys(v) {
|
|
1962
|
+
if (Array.isArray(v)) return v.map(sortKeys);
|
|
1963
|
+
if (v && typeof v === "object") {
|
|
1964
|
+
const src = v;
|
|
1965
|
+
const out = {};
|
|
1966
|
+
for (const k of Object.keys(src).sort()) {
|
|
1967
|
+
if (k === "createdAt" || k === "updatedAt") continue;
|
|
1968
|
+
out[k] = sortKeys(src[k]);
|
|
1969
|
+
}
|
|
1970
|
+
return out;
|
|
1971
|
+
}
|
|
1972
|
+
return v;
|
|
1973
|
+
}
|
|
1974
|
+
var crypto;
|
|
1975
|
+
var init_statehash = __esm({
|
|
1976
|
+
"src/core/statehash.ts"() {
|
|
1977
|
+
"use strict";
|
|
1978
|
+
crypto = __toESM(require("crypto"));
|
|
1979
|
+
init_specs2();
|
|
1980
|
+
}
|
|
1981
|
+
});
|
|
1982
|
+
|
|
1917
1983
|
// src/core/narrative-labels.ts
|
|
1918
1984
|
function resolveNarrativeLabels(methodName, steps) {
|
|
1919
1985
|
const errors = [];
|
|
@@ -7272,7 +7338,7 @@ var init_extensions = __esm({
|
|
|
7272
7338
|
});
|
|
7273
7339
|
|
|
7274
7340
|
// src/core/rules/types.ts
|
|
7275
|
-
var BUILTIN_PROFILES;
|
|
7341
|
+
var BUILTIN_PROFILES, PROJECT_KINDS;
|
|
7276
7342
|
var init_types = __esm({
|
|
7277
7343
|
"src/core/rules/types.ts"() {
|
|
7278
7344
|
"use strict";
|
|
@@ -7285,6 +7351,7 @@ var init_types = __esm({
|
|
|
7285
7351
|
"realtime-embedded",
|
|
7286
7352
|
"plc-cyclic"
|
|
7287
7353
|
];
|
|
7354
|
+
PROJECT_KINDS = ["fullstack", "system-of-systems", "monorepo"];
|
|
7288
7355
|
}
|
|
7289
7356
|
});
|
|
7290
7357
|
|
|
@@ -7475,47 +7542,6 @@ var init_filenames = __esm({
|
|
|
7475
7542
|
}
|
|
7476
7543
|
});
|
|
7477
7544
|
|
|
7478
|
-
// src/core/statehash.ts
|
|
7479
|
-
function computeStateId() {
|
|
7480
|
-
const tree = {
|
|
7481
|
-
system: loadSystemSpec(),
|
|
7482
|
-
subsystems: loadSubsystemSpecs(),
|
|
7483
|
-
components: loadComponentSpecs(),
|
|
7484
|
-
interfaces: loadInterfaceSpecs(),
|
|
7485
|
-
implementations: loadImplementationSpecs(),
|
|
7486
|
-
types: loadTypeSpecs()
|
|
7487
|
-
};
|
|
7488
|
-
const digest = crypto.createHash("sha256").update(canonicalize(tree)).digest("hex");
|
|
7489
|
-
return { algorithm: "sha256", digest };
|
|
7490
|
-
}
|
|
7491
|
-
function stateIdEquals(a, b) {
|
|
7492
|
-
return !!a && !!b && a.algorithm === b.algorithm && a.digest === b.digest;
|
|
7493
|
-
}
|
|
7494
|
-
function canonicalize(value) {
|
|
7495
|
-
return JSON.stringify(sortKeys(value));
|
|
7496
|
-
}
|
|
7497
|
-
function sortKeys(v) {
|
|
7498
|
-
if (Array.isArray(v)) return v.map(sortKeys);
|
|
7499
|
-
if (v && typeof v === "object") {
|
|
7500
|
-
const src = v;
|
|
7501
|
-
const out = {};
|
|
7502
|
-
for (const k of Object.keys(src).sort()) {
|
|
7503
|
-
if (k === "createdAt" || k === "updatedAt") continue;
|
|
7504
|
-
out[k] = sortKeys(src[k]);
|
|
7505
|
-
}
|
|
7506
|
-
return out;
|
|
7507
|
-
}
|
|
7508
|
-
return v;
|
|
7509
|
-
}
|
|
7510
|
-
var crypto;
|
|
7511
|
-
var init_statehash = __esm({
|
|
7512
|
-
"src/core/statehash.ts"() {
|
|
7513
|
-
"use strict";
|
|
7514
|
-
crypto = __toESM(require("crypto"));
|
|
7515
|
-
init_specs2();
|
|
7516
|
-
}
|
|
7517
|
-
});
|
|
7518
|
-
|
|
7519
7545
|
// src/core/openapi.ts
|
|
7520
7546
|
function schemaFor(typeRef, closureIds) {
|
|
7521
7547
|
const trimmed = typeRef.trim().replace(/^promise\s*<(.+)>$/i, "$1").trim();
|
|
@@ -7969,6 +7995,57 @@ function projectOwnSurface(maxAudience) {
|
|
|
7969
7995
|
function projectChildSurface() {
|
|
7970
7996
|
return projectOwnSurface("project");
|
|
7971
7997
|
}
|
|
7998
|
+
function localName(id) {
|
|
7999
|
+
return id.split("::").pop();
|
|
8000
|
+
}
|
|
8001
|
+
function projectSubsystemSurface(subsystemId) {
|
|
8002
|
+
const system = loadSystemSpec();
|
|
8003
|
+
if (!system) {
|
|
8004
|
+
throw new Error("Cannot project a subsystem surface: the L0 system spec is missing.");
|
|
8005
|
+
}
|
|
8006
|
+
const subsystems = loadSubsystemSpecs();
|
|
8007
|
+
const target = subsystems.find((s) => s.id === subsystemId);
|
|
8008
|
+
if (!target) {
|
|
8009
|
+
throw new Error(`Cannot project a subsystem surface: subsystem "${subsystemId}" does not exist.`);
|
|
8010
|
+
}
|
|
8011
|
+
const components = loadComponentSpecs();
|
|
8012
|
+
const interfaces = loadInterfaceSpecs();
|
|
8013
|
+
const types = loadTypeSpecs();
|
|
8014
|
+
const entries = [];
|
|
8015
|
+
for (const pub of target.publicInterfaces ?? []) {
|
|
8016
|
+
if (!pub.component) continue;
|
|
8017
|
+
const comp = components.find((c) => c.id === pub.component || c.id === `${subsystemId}::${pub.component}`);
|
|
8018
|
+
if (!comp) continue;
|
|
8019
|
+
if (comp.componentType !== "Portal") continue;
|
|
8020
|
+
const compInterfaces = interfaces.filter((i) => i.component === comp.id && (!pub.interface || i.id === pub.interface || i.id === `${subsystemId}::${pub.interface}`));
|
|
8021
|
+
const methods = compInterfaces.flatMap((i) => i.methods);
|
|
8022
|
+
entries.push({
|
|
8023
|
+
id: localName(pub.interface ?? comp.id),
|
|
8024
|
+
name: comp.name,
|
|
8025
|
+
// Family ceiling: a sibling surface is consumable by the system family only.
|
|
8026
|
+
audience: "project",
|
|
8027
|
+
type: pub.type ?? "Custom",
|
|
8028
|
+
// The snapshot carries the LOCAL portal name — consumers resolve cross-tree
|
|
8029
|
+
// refs by their final segment.
|
|
8030
|
+
component: localName(comp.id),
|
|
8031
|
+
methods,
|
|
8032
|
+
...comp.dispatch && comp.dispatch.length ? { dispatch: comp.dispatch } : {},
|
|
8033
|
+
// Project the backing Portal's auth + basePath so the codec can emit
|
|
8034
|
+
// OpenAPI security + per-portal servers self-contained from the snapshot.
|
|
8035
|
+
...comp.auth && comp.auth.scheme !== "none" ? { auth: comp.auth } : {},
|
|
8036
|
+
...comp.basePath ? { basePath: comp.basePath } : {},
|
|
8037
|
+
details: pub.details ?? ""
|
|
8038
|
+
});
|
|
8039
|
+
}
|
|
8040
|
+
return SurfaceSnapshotSchema.parse({
|
|
8041
|
+
projectName: `${system.name}::${subsystemId}`,
|
|
8042
|
+
origin: "generated",
|
|
8043
|
+
stateId: stateIdString(),
|
|
8044
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8045
|
+
interfaces: entries,
|
|
8046
|
+
types: computeTypeClosure(entries, types)
|
|
8047
|
+
});
|
|
8048
|
+
}
|
|
7972
8049
|
function listSnapshots(rootDir = getProjectRoot()) {
|
|
7973
8050
|
const dir = surfacesDir(rootDir);
|
|
7974
8051
|
if (!fs9.existsSync(dir)) return [];
|
|
@@ -7985,10 +8062,13 @@ function listSnapshots(rootDir = getProjectRoot()) {
|
|
|
7985
8062
|
function getSnapshot(projectName, rootDir = getProjectRoot()) {
|
|
7986
8063
|
return listSnapshots(rootDir).find((s) => s.projectName === projectName) ?? null;
|
|
7987
8064
|
}
|
|
8065
|
+
function snapshotFilename(projectName) {
|
|
8066
|
+
return `${safeFilenamePart(projectName)}.yaml`;
|
|
8067
|
+
}
|
|
7988
8068
|
function saveSnapshot(snapshot, rootDir = getProjectRoot()) {
|
|
7989
8069
|
const dir = surfacesDir(rootDir);
|
|
7990
8070
|
fs9.mkdirSync(dir, { recursive: true });
|
|
7991
|
-
const p = path10.join(dir,
|
|
8071
|
+
const p = path10.join(dir, snapshotFilename(snapshot.projectName));
|
|
7992
8072
|
writeYamlFile(p, SurfaceSnapshotSchema.parse(snapshot));
|
|
7993
8073
|
return p;
|
|
7994
8074
|
}
|
|
@@ -8061,17 +8141,54 @@ function importSurface(sourcePath, origin) {
|
|
|
8061
8141
|
return snapshot;
|
|
8062
8142
|
}
|
|
8063
8143
|
function generateChildSnapshots(rootDir = getProjectRoot()) {
|
|
8064
|
-
const
|
|
8144
|
+
const topLevel = loadSubsystemSpecs().filter((s) => !s.id.includes("::"));
|
|
8145
|
+
const children = topLevel.filter((s) => s.projectPath);
|
|
8065
8146
|
if (!children.length) return [];
|
|
8066
|
-
const
|
|
8147
|
+
const familySnapshot = projectChildSurface();
|
|
8148
|
+
const siblingSnapshots = /* @__PURE__ */ new Map();
|
|
8149
|
+
const siblingSurface = (subsystemId) => {
|
|
8150
|
+
let snap = siblingSnapshots.get(subsystemId);
|
|
8151
|
+
if (!snap) {
|
|
8152
|
+
snap = projectSubsystemSurface(subsystemId);
|
|
8153
|
+
siblingSnapshots.set(subsystemId, snap);
|
|
8154
|
+
}
|
|
8155
|
+
return snap;
|
|
8156
|
+
};
|
|
8067
8157
|
const written = [];
|
|
8068
8158
|
for (const child of children) {
|
|
8069
8159
|
const childDir = path10.resolve(rootDir, child.projectPath);
|
|
8070
8160
|
if (!fs9.existsSync(childDir)) continue;
|
|
8071
|
-
written.push(saveSnapshot(
|
|
8161
|
+
written.push(saveSnapshot(familySnapshot, childDir));
|
|
8162
|
+
for (const sibling of topLevel) {
|
|
8163
|
+
if (sibling.id === child.id) continue;
|
|
8164
|
+
written.push(saveSnapshot(siblingSurface(sibling.id), childDir));
|
|
8165
|
+
}
|
|
8072
8166
|
}
|
|
8073
8167
|
return written;
|
|
8074
8168
|
}
|
|
8169
|
+
function computeParentStateId(parentRoot) {
|
|
8170
|
+
return computeStateIdAt(parentRoot);
|
|
8171
|
+
}
|
|
8172
|
+
function listExternalInterfaces() {
|
|
8173
|
+
const snapshots = listSnapshots();
|
|
8174
|
+
const chainingParent = resolveChainingParent();
|
|
8175
|
+
const parentStateId = chainingParent ? computeParentStateId(chainingParent.parentRoot) : null;
|
|
8176
|
+
return snapshots.map((snapshot) => {
|
|
8177
|
+
const generated = snapshot.origin === "generated";
|
|
8178
|
+
const sourceKind = !generated ? "foreign" : snapshot.projectName.includes("::") ? "sibling" : "parent";
|
|
8179
|
+
const freshness = generated && parentStateId ? snapshot.stateId === parentStateId ? "fresh" : "stale" : "unverifiable";
|
|
8180
|
+
return {
|
|
8181
|
+
projectName: snapshot.projectName,
|
|
8182
|
+
origin: snapshot.origin,
|
|
8183
|
+
sourceKind,
|
|
8184
|
+
generatedAt: snapshot.generatedAt,
|
|
8185
|
+
...snapshot.stateId ? { stateId: snapshot.stateId } : {},
|
|
8186
|
+
...snapshot.version ? { version: snapshot.version } : {},
|
|
8187
|
+
freshness,
|
|
8188
|
+
interfaceIds: snapshot.interfaces.map((e) => e.id)
|
|
8189
|
+
};
|
|
8190
|
+
});
|
|
8191
|
+
}
|
|
8075
8192
|
function surfaceContentKey(snapshot) {
|
|
8076
8193
|
const { stateId, generatedAt, origin, ...content } = snapshot;
|
|
8077
8194
|
return JSON.stringify(content);
|
|
@@ -8280,7 +8397,8 @@ var init_contracts = __esm({
|
|
|
8280
8397
|
"SURFACE_REF_NOT_EXPOSED",
|
|
8281
8398
|
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches capability "${step.capability}" through cross-tree portal "${step.targetComponent}" (step ${step.stepNumber}), but the surface snapshot of "${resolved.snapshot.projectName}" does not serve that capability on "${resolved.entry.id}".`,
|
|
8282
8399
|
impl.id,
|
|
8283
|
-
isDraftCtx
|
|
8400
|
+
isDraftCtx,
|
|
8401
|
+
true
|
|
8284
8402
|
);
|
|
8285
8403
|
}
|
|
8286
8404
|
continue;
|
|
@@ -8337,7 +8455,8 @@ var init_contracts = __esm({
|
|
|
8337
8455
|
"SURFACE_REF_NOT_EXPOSED",
|
|
8338
8456
|
`Method "${implMethod.name}" in implementation "${impl.id}" calls "${step.targetMethod}" on cross-tree component "${step.targetComponent}" (step ${step.stepNumber}), but the surface snapshot of "${resolved.snapshot.projectName}" does not expose that method on "${resolved.entry.id}".`,
|
|
8339
8457
|
impl.id,
|
|
8340
|
-
isDraftCtx
|
|
8458
|
+
isDraftCtx,
|
|
8459
|
+
true
|
|
8341
8460
|
);
|
|
8342
8461
|
} else if (step.assertsGuarantees) {
|
|
8343
8462
|
const declared = new Set(surfaceMethod.guarantees ?? []);
|
|
@@ -8348,7 +8467,8 @@ var init_contracts = __esm({
|
|
|
8348
8467
|
"NARRATIVE_SEMANTIC_UNBACKED",
|
|
8349
8468
|
`Step ${step.stepNumber} of "${implMethod.name}" in implementation "${impl.id}" asserts guarantee "${g}", but the surface snapshot of "${resolved.snapshot.projectName}" does not declare it on "${resolved.entry.id}.${step.targetMethod}".`,
|
|
8350
8469
|
impl.id,
|
|
8351
|
-
isDraftCtx
|
|
8470
|
+
isDraftCtx,
|
|
8471
|
+
true
|
|
8352
8472
|
);
|
|
8353
8473
|
}
|
|
8354
8474
|
}
|
|
@@ -9836,7 +9956,8 @@ var init_stereotype_deps = __esm({
|
|
|
9836
9956
|
"CROSS_SUBSYSTEM_NON_ADAPTER",
|
|
9837
9957
|
`Boundary violation: ${comp.componentType} "${comp.id}" depends directly on "${depId}", a surface of project "${resolved.snapshot.projectName}". Only a local client Adapter may cross a project boundary \u2014 route this hop through an Adapter.`,
|
|
9838
9958
|
comp.id,
|
|
9839
|
-
isDraftCtx
|
|
9959
|
+
isDraftCtx,
|
|
9960
|
+
true
|
|
9840
9961
|
);
|
|
9841
9962
|
}
|
|
9842
9963
|
continue;
|
|
@@ -10414,14 +10535,14 @@ var init_declarative_assertions = __esm({
|
|
|
10414
10535
|
});
|
|
10415
10536
|
|
|
10416
10537
|
// src/core/rules/profiles.ts
|
|
10417
|
-
var BACKEND_LIKE, FRONTEND_LIKE,
|
|
10538
|
+
var BACKEND_LIKE, FRONTEND_LIKE, PROJECT_KINDS2, profilesRule;
|
|
10418
10539
|
var init_profiles = __esm({
|
|
10419
10540
|
"src/core/rules/profiles.ts"() {
|
|
10420
10541
|
"use strict";
|
|
10421
10542
|
init_types();
|
|
10422
10543
|
BACKEND_LIKE = /* @__PURE__ */ new Set(["backend", "lowlevel-os", "game-ecs", "realtime-embedded", "plc-cyclic"]);
|
|
10423
10544
|
FRONTEND_LIKE = /* @__PURE__ */ new Set(["frontend-reactive", "frontend-controller"]);
|
|
10424
|
-
|
|
10545
|
+
PROJECT_KINDS2 = new Set(PROJECT_KINDS);
|
|
10425
10546
|
profilesRule = {
|
|
10426
10547
|
name: "architectural-profiles",
|
|
10427
10548
|
description: "Per-profile stereotype constraints: View/FeatureComponent/RouterComponent only in frontend profiles; Actor/Supervisor forbidden in plc-cyclic (single scan cycle); Actor/Supervisor in frontend profiles warned. Extension packs may register custom profiles (family + forbidden/discouraged stereotype lists); unknown profile names are flagged.",
|
|
@@ -10445,7 +10566,7 @@ var init_profiles = __esm({
|
|
|
10445
10566
|
);
|
|
10446
10567
|
}
|
|
10447
10568
|
}
|
|
10448
|
-
if (!registered.has(ctx.projectType) && !
|
|
10569
|
+
if (!registered.has(ctx.projectType) && !PROJECT_KINDS2.has(ctx.projectType)) {
|
|
10449
10570
|
ctx.addIssue(
|
|
10450
10571
|
"warning",
|
|
10451
10572
|
"UNKNOWN_PROFILE",
|
|
@@ -11699,11 +11820,11 @@ var init_narrative_antipatterns = __esm({
|
|
|
11699
11820
|
const memberEdges = keys.flatMap((k) => (adjacency.get(k) ?? []).filter((e) => inScc.has(e.toKey)));
|
|
11700
11821
|
if (memberEdges.length === 0) continue;
|
|
11701
11822
|
const anchor = [...memberEdges].sort((a, b) => a.fromKey.localeCompare(b.fromKey))[0];
|
|
11702
|
-
const
|
|
11823
|
+
const path61 = [...keys].sort().join(" \u2192 ");
|
|
11703
11824
|
ctx.addIssue(
|
|
11704
11825
|
"warning",
|
|
11705
11826
|
"UNCONDITIONAL_CALL_CYCLE",
|
|
11706
|
-
`Call cycle with no guard: ${
|
|
11827
|
+
`Call cycle with no guard: ${path61} \u2014 every call edge in this cycle is unavoidable on all paths of its narrative (e.g. step ${anchor.stepNumber} of "${anchor.methodName}" in "${anchor.impl.id}" always calls ${anchor.toLabel}). This recurses without a base case, by construction. Guard at least one edge with a branch/return before the call, or lint.allow with the termination argument.`,
|
|
11707
11828
|
anchor.impl.id,
|
|
11708
11829
|
memberEdges.some((e) => ctx.isImplementationDraft(e.impl))
|
|
11709
11830
|
);
|
|
@@ -13090,9 +13211,15 @@ function buildRuleContext(opts) {
|
|
|
13090
13211
|
...[...SDD_RULES, ...extensions.rules].flatMap((r) => r.codes.map((c) => c.code)),
|
|
13091
13212
|
// Declarative assertions bring their own namespaced codes — lint.allow
|
|
13092
13213
|
// and severity overrides treat them exactly like builtins.
|
|
13093
|
-
...extensions.assertions.map((a) => a.fullCode)
|
|
13214
|
+
...extensions.assertions.map((a) => a.fullCode),
|
|
13215
|
+
// Entry-point emitted codes: validateSddTree's chained-subproject pass
|
|
13216
|
+
// raises these AFTER the rule run (it post-processes the aggregated issue
|
|
13217
|
+
// list), so no registered rule declares them — but lint.allow validation
|
|
13218
|
+
// must still recognize them as real codes.
|
|
13219
|
+
"CHAINED_SUBPROJECT_CONTEXT",
|
|
13220
|
+
"UNVERIFIED_EXTERNAL_REF"
|
|
13094
13221
|
]);
|
|
13095
|
-
const addIssue = (defaultSeverity, code, message, specId, isDraftContext) => {
|
|
13222
|
+
const addIssue = (defaultSeverity, code, message, specId, isDraftContext, surfaceResolved) => {
|
|
13096
13223
|
if (scopeSubsystem && specId && !isSpecInScope(specId)) {
|
|
13097
13224
|
return;
|
|
13098
13225
|
}
|
|
@@ -13110,7 +13237,14 @@ function buildRuleContext(opts) {
|
|
|
13110
13237
|
if (severity === "warning") return;
|
|
13111
13238
|
}
|
|
13112
13239
|
}
|
|
13113
|
-
issues.push({
|
|
13240
|
+
issues.push({
|
|
13241
|
+
severity,
|
|
13242
|
+
code,
|
|
13243
|
+
message,
|
|
13244
|
+
specId,
|
|
13245
|
+
...isDraftContext ? { draftContext: true } : {},
|
|
13246
|
+
...surfaceResolved ? { surfaceResolved: true } : {}
|
|
13247
|
+
});
|
|
13114
13248
|
};
|
|
13115
13249
|
return {
|
|
13116
13250
|
system,
|
|
@@ -13535,24 +13669,54 @@ function validateSddTree(rulesOrOptions, projectType = "backend") {
|
|
|
13535
13669
|
for (const rule of ruleSequence()) {
|
|
13536
13670
|
rule.check(ctx);
|
|
13537
13671
|
}
|
|
13538
|
-
const hasCrossTreeSuspects = issues.some(
|
|
13672
|
+
const hasCrossTreeSuspects = issues.some(
|
|
13673
|
+
(i) => SUBPROJECT_REFERENCE_CODES.has(i.code) || SUBPROJECT_CONFORMANCE_CODES.has(i.code)
|
|
13674
|
+
);
|
|
13539
13675
|
const chainingParent = hasCrossTreeSuspects ? findChainingParent(getProjectRoot()) : null;
|
|
13540
13676
|
if (chainingParent) {
|
|
13677
|
+
let unverified = 0;
|
|
13541
13678
|
let downgraded = 0;
|
|
13542
|
-
for (
|
|
13543
|
-
|
|
13544
|
-
if (iss.
|
|
13545
|
-
|
|
13546
|
-
|
|
13679
|
+
for (let at = 0; at < issues.length; at++) {
|
|
13680
|
+
const iss = issues[at];
|
|
13681
|
+
if (SUBPROJECT_REFERENCE_CODES.has(iss.code) && !iss.surfaceResolved) {
|
|
13682
|
+
issues[at] = {
|
|
13683
|
+
severity: "warning",
|
|
13684
|
+
code: "UNVERIFIED_EXTERNAL_REF",
|
|
13685
|
+
crossTreeContext: true,
|
|
13686
|
+
// --ci waives it (parent root is authoritative)
|
|
13687
|
+
specId: iss.specId,
|
|
13688
|
+
...iss.agentId ? { agentId: iss.agentId } : {},
|
|
13689
|
+
...iss.draftContext ? { draftContext: true } : {},
|
|
13690
|
+
message: `Unverified external reference (${iss.code}): ${iss.message} No vendored surface snapshot covers this reference, so it cannot be verified from this chained subproject standalone \u2014 re-lock the parent so fresh family/sibling snapshots ship, or inspect what this project can consume via \`wairon surface externals\` / sdd_list_external_interfaces.`
|
|
13691
|
+
};
|
|
13692
|
+
unverified++;
|
|
13693
|
+
continue;
|
|
13694
|
+
}
|
|
13695
|
+
if (SUBPROJECT_CONFORMANCE_CODES.has(iss.code)) {
|
|
13696
|
+
if (iss.severity === "error") {
|
|
13697
|
+
iss.severity = "warning";
|
|
13698
|
+
downgraded++;
|
|
13699
|
+
}
|
|
13700
|
+
iss.crossTreeContext = true;
|
|
13547
13701
|
}
|
|
13548
|
-
iss.crossTreeContext = true;
|
|
13549
13702
|
}
|
|
13550
|
-
if (downgraded > 0) {
|
|
13703
|
+
if (unverified > 0 || downgraded > 0) {
|
|
13704
|
+
const notes = [];
|
|
13705
|
+
if (unverified > 0) {
|
|
13706
|
+
notes.push(
|
|
13707
|
+
`${unverified} cross-tree reference(s) have no vendored surface snapshot covering them and were reported as UNVERIFIED_EXTERNAL_REF warnings \u2014 re-lock the parent so fresh family/sibling snapshots ship, or inspect via \`wairon surface externals\` / sdd_list_external_interfaces.`
|
|
13708
|
+
);
|
|
13709
|
+
}
|
|
13710
|
+
if (downgraded > 0) {
|
|
13711
|
+
notes.push(
|
|
13712
|
+
`${downgraded} code\u2194spec conformance finding(s) (parent-root-relative source paths) were downgraded to warnings.`
|
|
13713
|
+
);
|
|
13714
|
+
}
|
|
13551
13715
|
issues.unshift({
|
|
13552
13716
|
severity: "warning",
|
|
13553
13717
|
code: "CHAINED_SUBPROJECT_CONTEXT",
|
|
13554
13718
|
crossTreeContext: true,
|
|
13555
|
-
message: `This project is a chained subproject ("${chainingParent.subsystemId}") of the parent project at "${chainingParent.parentRoot}". ${
|
|
13719
|
+
message: `This project is a chained subproject ("${chainingParent.subsystemId}") of the parent project at "${chainingParent.parentRoot}". ${notes.join(" ")} Full cross-tree verification runs from the parent root.`
|
|
13556
13720
|
});
|
|
13557
13721
|
}
|
|
13558
13722
|
}
|
|
@@ -13569,7 +13733,7 @@ function validateSddTree(rulesOrOptions, projectType = "backend") {
|
|
|
13569
13733
|
function validateAsComplete(options) {
|
|
13570
13734
|
return validateSddTree({ ...options ?? {}, treatAllAsComplete: true });
|
|
13571
13735
|
}
|
|
13572
|
-
var
|
|
13736
|
+
var SUBPROJECT_REFERENCE_CODES, SUBPROJECT_CONFORMANCE_CODES;
|
|
13573
13737
|
var init_validation = __esm({
|
|
13574
13738
|
"src/core/validation.ts"() {
|
|
13575
13739
|
"use strict";
|
|
@@ -13582,8 +13746,7 @@ var init_validation = __esm({
|
|
|
13582
13746
|
init_source_analysis();
|
|
13583
13747
|
init_specs2();
|
|
13584
13748
|
init_fs();
|
|
13585
|
-
|
|
13586
|
-
// reference resolution
|
|
13749
|
+
SUBPROJECT_REFERENCE_CODES = /* @__PURE__ */ new Set([
|
|
13587
13750
|
"UNDEFINED_TYPE_REFERENCE",
|
|
13588
13751
|
"INVALID_DEPENDENCY_REFERENCE",
|
|
13589
13752
|
"INVALID_TARGET_COMPONENT_REFERENCE",
|
|
@@ -13591,8 +13754,9 @@ var init_validation = __esm({
|
|
|
13591
13754
|
"UNDECLARED_DEPENDENCY_CALL",
|
|
13592
13755
|
"INVALID_TRUSTED_LINK",
|
|
13593
13756
|
"CROSS_SUBSYSTEM_NON_ADAPTER",
|
|
13594
|
-
"CROSS_TREE_REF_UNRESOLVED"
|
|
13595
|
-
|
|
13757
|
+
"CROSS_TREE_REF_UNRESOLVED"
|
|
13758
|
+
]);
|
|
13759
|
+
SUBPROJECT_CONFORMANCE_CODES = /* @__PURE__ */ new Set([
|
|
13596
13760
|
"MISSING_SOURCE_FILE",
|
|
13597
13761
|
"SOURCE_PATH_ESCAPES_ROOT",
|
|
13598
13762
|
"MISSING_SOURCE_PATH",
|
|
@@ -14134,6 +14298,7 @@ __export(specs_exports, {
|
|
|
14134
14298
|
buildProjectGraph: () => buildProjectGraph,
|
|
14135
14299
|
clearLoaderIssues: () => clearLoaderIssues,
|
|
14136
14300
|
collectPromotableSpecs: () => collectPromotableSpecs,
|
|
14301
|
+
computeStateIdAt: () => computeStateIdAt,
|
|
14137
14302
|
deleteComponentSpec: () => deleteComponentSpec,
|
|
14138
14303
|
deleteGroupSpec: () => deleteGroupSpec,
|
|
14139
14304
|
deleteImplementationSpec: () => deleteImplementationSpec,
|
|
@@ -14166,6 +14331,7 @@ __export(specs_exports, {
|
|
|
14166
14331
|
loadTypeSpec: () => loadTypeSpec,
|
|
14167
14332
|
loadTypeSpecs: () => loadTypeSpecs,
|
|
14168
14333
|
normalizeComponentLayout: () => normalizeComponentLayout,
|
|
14334
|
+
resolveChainingParent: () => resolveChainingParent,
|
|
14169
14335
|
resolveSubprojectForNamespace: () => resolveSubprojectForNamespace,
|
|
14170
14336
|
restoreSpecFiles: () => restoreSpecFiles,
|
|
14171
14337
|
saveComponentSpec: () => saveComponentSpec,
|
|
@@ -14570,6 +14736,19 @@ function dryRunSerializeSpecs(include) {
|
|
|
14570
14736
|
function buildProjectGraph(level) {
|
|
14571
14737
|
return buildGraphModel(level);
|
|
14572
14738
|
}
|
|
14739
|
+
function resolveChainingParent() {
|
|
14740
|
+
return findChainingParent(getProjectRoot());
|
|
14741
|
+
}
|
|
14742
|
+
function computeStateIdAt(root) {
|
|
14743
|
+
const resolved = path14.resolve(root);
|
|
14744
|
+
return runWithProjectRoot(resolved, () => {
|
|
14745
|
+
workspaceFor(resolved).invalidate();
|
|
14746
|
+
const system = loadSystemSpec();
|
|
14747
|
+
if (!system) return null;
|
|
14748
|
+
const s = computeStateId();
|
|
14749
|
+
return `${s.algorithm}:${s.digest}`;
|
|
14750
|
+
});
|
|
14751
|
+
}
|
|
14573
14752
|
function deleteTypeSpec(id) {
|
|
14574
14753
|
return current().deleteTypeSpec(id);
|
|
14575
14754
|
}
|
|
@@ -14613,6 +14792,7 @@ var init_specs2 = __esm({
|
|
|
14613
14792
|
path14 = __toESM(require("path"));
|
|
14614
14793
|
init_loader();
|
|
14615
14794
|
init_fs();
|
|
14795
|
+
init_statehash();
|
|
14616
14796
|
init_yaml();
|
|
14617
14797
|
init_models();
|
|
14618
14798
|
init_narrative_labels();
|
|
@@ -17206,6 +17386,9 @@ function requireSpecs() {
|
|
|
17206
17386
|
function requireProvision() {
|
|
17207
17387
|
return init_provision(), __toCommonJS(provision_exports);
|
|
17208
17388
|
}
|
|
17389
|
+
function listExternalInterfaces2() {
|
|
17390
|
+
return listExternalInterfaces();
|
|
17391
|
+
}
|
|
17209
17392
|
function text(content) {
|
|
17210
17393
|
return { content: [{ type: "text", text: content }] };
|
|
17211
17394
|
}
|
|
@@ -18172,7 +18355,36 @@ NOTICE:
|
|
|
18172
18355
|
}
|
|
18173
18356
|
}
|
|
18174
18357
|
);
|
|
18358
|
+
reg(
|
|
18359
|
+
server,
|
|
18360
|
+
"sdd_list_external_interfaces",
|
|
18361
|
+
{
|
|
18362
|
+
description: "List the bound project's consumable external surfaces (parent family, siblings, foreign imports) as discovery entries with origin, provenance, and freshness \u2014 the tool an agent inside a subproject uses to SEE its outward world instead of discovering it by failed reference resolution. Full contracts stay in the vendored snapshots (.wai/surfaces/); each entry summarizes the interface ids it exposes."
|
|
18363
|
+
},
|
|
18364
|
+
() => {
|
|
18365
|
+
try {
|
|
18366
|
+
return json(listExternalInterfaces2());
|
|
18367
|
+
} catch (e) {
|
|
18368
|
+
return errText(String(e));
|
|
18369
|
+
}
|
|
18370
|
+
}
|
|
18371
|
+
);
|
|
18175
18372
|
registerSkillResources(server);
|
|
18373
|
+
try {
|
|
18374
|
+
const chainingParent = resolveChainingParent();
|
|
18375
|
+
if (chainingParent) {
|
|
18376
|
+
let externalSurfaceCount = 0;
|
|
18377
|
+
try {
|
|
18378
|
+
externalSurfaceCount = listExternalInterfaces2().length;
|
|
18379
|
+
} catch {
|
|
18380
|
+
}
|
|
18381
|
+
process.stderr.write(
|
|
18382
|
+
`[wairon mcp] chained subproject: this root is mounted as subsystem "${chainingParent.subsystemId}" of the parent project at ${chainingParent.parentRoot} \u2014 ${externalSurfaceCount} vendored external surface(s) discoverable via sdd_list_external_interfaces
|
|
18383
|
+
`
|
|
18384
|
+
);
|
|
18385
|
+
}
|
|
18386
|
+
} catch {
|
|
18387
|
+
}
|
|
18176
18388
|
if (options.hostedTools) {
|
|
18177
18389
|
const hostedStub = () => errText("This hosted tool is dispatched by the hosting data plane before reaching the MCP server; it is unavailable outside a hosted request.");
|
|
18178
18390
|
reg(server, "sdd_host_lock_project", {
|
|
@@ -18308,6 +18520,8 @@ var init_server = __esm({
|
|
|
18308
18520
|
init_narrative_labels();
|
|
18309
18521
|
init_specs();
|
|
18310
18522
|
init_skills();
|
|
18523
|
+
init_specs2();
|
|
18524
|
+
init_surfaces();
|
|
18311
18525
|
SERVER_BUILD_STAMP = captureBuildStamp(__filename);
|
|
18312
18526
|
STALE_SERVER_WARNING = "\n\n\u26A0 STALE SERVER: the wairon build on disk changed after this MCP server started. Restart the MCP session (e.g. /mcp reconnect) before further spec edits \u2014 writes through a stale server can silently drop fields introduced by newer schemas.";
|
|
18313
18527
|
SKILL_RESOURCE_MIME = "text/markdown";
|
|
@@ -21381,7 +21595,7 @@ var require_dist = __commonJS({
|
|
|
21381
21595
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
21382
21596
|
}
|
|
21383
21597
|
var fs51 = __toESM2(require("fs"));
|
|
21384
|
-
var
|
|
21598
|
+
var path61 = __toESM2(require("path"));
|
|
21385
21599
|
var import_fflate = require_node();
|
|
21386
21600
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".hg", ".svn"]);
|
|
21387
21601
|
function listEntries(archive) {
|
|
@@ -21419,8 +21633,8 @@ var require_dist = __commonJS({
|
|
|
21419
21633
|
}
|
|
21420
21634
|
function writeTree(destDir, files) {
|
|
21421
21635
|
for (const file of files) {
|
|
21422
|
-
const absolute =
|
|
21423
|
-
fs51.mkdirSync(
|
|
21636
|
+
const absolute = path61.join(destDir, file.path);
|
|
21637
|
+
fs51.mkdirSync(path61.dirname(absolute), { recursive: true });
|
|
21424
21638
|
fs51.writeFileSync(absolute, file.contents);
|
|
21425
21639
|
}
|
|
21426
21640
|
}
|
|
@@ -21433,10 +21647,10 @@ var require_dist = __commonJS({
|
|
|
21433
21647
|
for (const entry of fs51.readdirSync(current2, { withFileTypes: true })) {
|
|
21434
21648
|
if (entry.isDirectory()) {
|
|
21435
21649
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
21436
|
-
walkPackDir(root,
|
|
21650
|
+
walkPackDir(root, path61.join(current2, entry.name), out);
|
|
21437
21651
|
} else if (entry.isFile()) {
|
|
21438
|
-
const absolute =
|
|
21439
|
-
const relative22 =
|
|
21652
|
+
const absolute = path61.join(current2, entry.name);
|
|
21653
|
+
const relative22 = path61.relative(root, absolute).split(path61.sep).join("/");
|
|
21440
21654
|
out.push({ path: relative22, contents: fs51.readFileSync(absolute) });
|
|
21441
21655
|
}
|
|
21442
21656
|
}
|
|
@@ -22847,86 +23061,216 @@ async function generateLayer(options = {}) {
|
|
|
22847
23061
|
}
|
|
22848
23062
|
|
|
22849
23063
|
// src/commands/lock.ts
|
|
23064
|
+
var os7 = __toESM(require("os"));
|
|
22850
23065
|
var import_inquirer2 = __toESM(require("inquirer"));
|
|
22851
23066
|
init_logger();
|
|
22852
|
-
|
|
22853
|
-
|
|
22854
|
-
|
|
22855
|
-
|
|
22856
|
-
|
|
22857
|
-
|
|
22858
|
-
|
|
22859
|
-
|
|
22860
|
-
|
|
23067
|
+
init_defaults();
|
|
23068
|
+
|
|
23069
|
+
// src/core/detection.ts
|
|
23070
|
+
var fs18 = __toESM(require("fs"));
|
|
23071
|
+
var path27 = __toESM(require("path"));
|
|
23072
|
+
init_defaults();
|
|
23073
|
+
var PACKAGE_MARKERS = [
|
|
23074
|
+
"package.json",
|
|
23075
|
+
"pyproject.toml",
|
|
23076
|
+
"Cargo.toml",
|
|
23077
|
+
"go.mod",
|
|
23078
|
+
"build.gradle",
|
|
23079
|
+
"build.gradle.kts",
|
|
23080
|
+
"pom.xml"
|
|
23081
|
+
];
|
|
23082
|
+
var MAX_SCAN_DEPTH = 5;
|
|
23083
|
+
function detectDomainCandidates(projectRoot2, alreadyTrackedPaths = /* @__PURE__ */ new Set(), alreadyTrackedIds = /* @__PURE__ */ new Set()) {
|
|
23084
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
23085
|
+
for (const c of detectGitSubmodules(projectRoot2)) {
|
|
23086
|
+
candidates.set(c.path, { ...c, alreadyTracked: alreadyTrackedPaths.has(c.path) });
|
|
22861
23087
|
}
|
|
22862
|
-
const
|
|
22863
|
-
|
|
22864
|
-
|
|
22865
|
-
const promotable = collectPromotableSpecs(options.subsystem);
|
|
22866
|
-
const originalStatuses = /* @__PURE__ */ new Map();
|
|
22867
|
-
const isSpecInSubsystemScope = (specSubsystem) => {
|
|
22868
|
-
if (!options.subsystem) return true;
|
|
22869
|
-
if (!specSubsystem) return false;
|
|
22870
|
-
return specSubsystem === options.subsystem || specSubsystem.startsWith(`${options.subsystem}::`);
|
|
22871
|
-
};
|
|
22872
|
-
for (const s of index.subsystems) {
|
|
22873
|
-
if (!options.subsystem || s.id === options.subsystem || s.id.startsWith(`${options.subsystem}::`)) {
|
|
22874
|
-
originalStatuses.set(s, s.status);
|
|
22875
|
-
s.status = "complete";
|
|
23088
|
+
for (const c of detectNestedGitRepos(projectRoot2)) {
|
|
23089
|
+
if (!candidates.has(c.path)) {
|
|
23090
|
+
candidates.set(c.path, { ...c, alreadyTracked: alreadyTrackedPaths.has(c.path) });
|
|
22876
23091
|
}
|
|
22877
23092
|
}
|
|
22878
|
-
|
|
22879
|
-
|
|
22880
|
-
|
|
22881
|
-
|
|
22882
|
-
|
|
23093
|
+
const gitPaths = new Set(
|
|
23094
|
+
Array.from(candidates.values()).filter((c) => c.type === "git-submodule" || c.type === "git-repo").map((c) => c.path)
|
|
23095
|
+
);
|
|
23096
|
+
for (const c of detectPackageRoots(projectRoot2)) {
|
|
23097
|
+
if (candidates.has(c.path)) continue;
|
|
23098
|
+
const insideGit = Array.from(gitPaths).some(
|
|
23099
|
+
(gp) => c.path === gp || c.path.startsWith(gp + "/")
|
|
23100
|
+
);
|
|
23101
|
+
if (insideGit) continue;
|
|
23102
|
+
candidates.set(c.path, { ...c, alreadyTracked: alreadyTrackedPaths.has(c.path) });
|
|
22883
23103
|
}
|
|
22884
|
-
|
|
22885
|
-
|
|
22886
|
-
|
|
22887
|
-
|
|
22888
|
-
|
|
22889
|
-
|
|
23104
|
+
const sorted = Array.from(candidates.values()).sort((a, b) => a.path.localeCompare(b.path));
|
|
23105
|
+
return deduplicateIds(sorted, alreadyTrackedIds);
|
|
23106
|
+
}
|
|
23107
|
+
function deduplicateIds(candidates, existingIds = /* @__PURE__ */ new Set()) {
|
|
23108
|
+
const idCount = /* @__PURE__ */ new Map();
|
|
23109
|
+
for (const id of existingIds) {
|
|
23110
|
+
idCount.set(id, (idCount.get(id) ?? 0) + 1);
|
|
22890
23111
|
}
|
|
22891
|
-
for (const
|
|
22892
|
-
|
|
22893
|
-
const comp = intf ? index.components.find((c) => c.id === intf.component) : null;
|
|
22894
|
-
if (comp && isSpecInSubsystemScope(comp.subsystem)) {
|
|
22895
|
-
originalStatuses.set(m, m.status);
|
|
22896
|
-
m.status = "complete";
|
|
22897
|
-
}
|
|
23112
|
+
for (const c of candidates) {
|
|
23113
|
+
idCount.set(c.suggestedId, (idCount.get(c.suggestedId) ?? 0) + 1);
|
|
22898
23114
|
}
|
|
22899
|
-
|
|
22900
|
-
|
|
22901
|
-
|
|
22902
|
-
|
|
22903
|
-
|
|
23115
|
+
return candidates.map((c) => {
|
|
23116
|
+
if ((idCount.get(c.suggestedId) ?? 0) <= 1) return c;
|
|
23117
|
+
const parts = c.path.split("/");
|
|
23118
|
+
const qualifiedId2 = parts.length >= 2 ? pathToId(`${parts[parts.length - 2]}-${parts[parts.length - 1]}`) : c.suggestedId;
|
|
23119
|
+
return { ...c, suggestedId: qualifiedId2 };
|
|
22904
23120
|
});
|
|
22905
|
-
|
|
22906
|
-
|
|
23121
|
+
}
|
|
23122
|
+
function parseGitmodules(filePath) {
|
|
23123
|
+
const content = fs18.readFileSync(filePath, "utf-8");
|
|
23124
|
+
const entries = [];
|
|
23125
|
+
let current2 = {};
|
|
23126
|
+
for (const line2 of content.split("\n")) {
|
|
23127
|
+
const trimmed = line2.trim();
|
|
23128
|
+
const headerMatch = trimmed.match(/^\[submodule "(.+)"\]$/);
|
|
23129
|
+
if (headerMatch) {
|
|
23130
|
+
if (current2.path) entries.push(current2);
|
|
23131
|
+
current2 = { name: headerMatch[1] };
|
|
23132
|
+
continue;
|
|
23133
|
+
}
|
|
23134
|
+
const keyVal = trimmed.match(/^(\w+)\s*=\s*(.+)$/);
|
|
23135
|
+
if (keyVal) {
|
|
23136
|
+
const [, key, value] = keyVal;
|
|
23137
|
+
if (key === "path") current2.path = value.trim();
|
|
23138
|
+
if (key === "url") current2.url = value.trim();
|
|
23139
|
+
}
|
|
22907
23140
|
}
|
|
22908
|
-
|
|
22909
|
-
|
|
22910
|
-
|
|
22911
|
-
|
|
22912
|
-
|
|
22913
|
-
|
|
22914
|
-
|
|
22915
|
-
|
|
22916
|
-
|
|
22917
|
-
|
|
22918
|
-
|
|
22919
|
-
|
|
22920
|
-
|
|
23141
|
+
if (current2.path) entries.push(current2);
|
|
23142
|
+
return entries;
|
|
23143
|
+
}
|
|
23144
|
+
function detectGitSubmodules(projectRoot2) {
|
|
23145
|
+
const gitmodulesPath = path27.join(projectRoot2, ".gitmodules");
|
|
23146
|
+
if (!fs18.existsSync(gitmodulesPath)) return [];
|
|
23147
|
+
return parseGitmodules(gitmodulesPath).map((entry) => ({
|
|
23148
|
+
suggestedId: pathToId(entry.path),
|
|
23149
|
+
suggestedName: pathToName(entry.path),
|
|
23150
|
+
path: normalizePath3(entry.path),
|
|
23151
|
+
type: "git-submodule",
|
|
23152
|
+
alreadyTracked: false
|
|
23153
|
+
}));
|
|
23154
|
+
}
|
|
23155
|
+
function detectNestedGitRepos(projectRoot2) {
|
|
23156
|
+
const results = [];
|
|
23157
|
+
walkForGit(projectRoot2, projectRoot2, 0, results);
|
|
23158
|
+
return results;
|
|
23159
|
+
}
|
|
23160
|
+
function walkForGit(projectRoot2, currentDir, depth, results) {
|
|
23161
|
+
if (depth > MAX_SCAN_DEPTH) return;
|
|
23162
|
+
let entries;
|
|
23163
|
+
try {
|
|
23164
|
+
entries = fs18.readdirSync(currentDir, { withFileTypes: true });
|
|
23165
|
+
} catch {
|
|
23166
|
+
return;
|
|
23167
|
+
}
|
|
23168
|
+
for (const entry of entries) {
|
|
23169
|
+
if (!entry.isDirectory()) continue;
|
|
23170
|
+
if (SCAN_EXCLUDE_DIRS.has(entry.name)) continue;
|
|
23171
|
+
const fullPath = path27.join(currentDir, entry.name);
|
|
23172
|
+
const relPath = normalizePath3(path27.relative(projectRoot2, fullPath));
|
|
23173
|
+
if (relPath === "" || relPath === ".") continue;
|
|
23174
|
+
const gitPath = path27.join(fullPath, ".git");
|
|
23175
|
+
if (fs18.existsSync(gitPath)) {
|
|
23176
|
+
results.push({
|
|
23177
|
+
suggestedId: pathToId(relPath),
|
|
23178
|
+
suggestedName: pathToName(relPath),
|
|
23179
|
+
path: relPath,
|
|
23180
|
+
type: "git-repo",
|
|
23181
|
+
alreadyTracked: false
|
|
23182
|
+
});
|
|
23183
|
+
continue;
|
|
22921
23184
|
}
|
|
22922
|
-
|
|
22923
|
-
|
|
23185
|
+
walkForGit(projectRoot2, fullPath, depth + 1, results);
|
|
23186
|
+
}
|
|
23187
|
+
}
|
|
23188
|
+
function detectPackageRoots(projectRoot2) {
|
|
23189
|
+
const results = [];
|
|
23190
|
+
walkForPackages(projectRoot2, projectRoot2, 0, results);
|
|
23191
|
+
return results;
|
|
23192
|
+
}
|
|
23193
|
+
function walkForPackages(projectRoot2, currentDir, depth, results) {
|
|
23194
|
+
if (depth > MAX_SCAN_DEPTH) return;
|
|
23195
|
+
let entries;
|
|
23196
|
+
try {
|
|
23197
|
+
entries = fs18.readdirSync(currentDir, { withFileTypes: true });
|
|
23198
|
+
} catch {
|
|
23199
|
+
return;
|
|
23200
|
+
}
|
|
23201
|
+
for (const entry of entries) {
|
|
23202
|
+
if (!entry.isDirectory()) continue;
|
|
23203
|
+
if (SCAN_EXCLUDE_DIRS.has(entry.name)) continue;
|
|
23204
|
+
const fullPath = path27.join(currentDir, entry.name);
|
|
23205
|
+
const relPath = normalizePath3(path27.relative(projectRoot2, fullPath));
|
|
23206
|
+
if (relPath === "" || relPath === ".") continue;
|
|
23207
|
+
const hasMarker = PACKAGE_MARKERS.some((m) => fs18.existsSync(path27.join(fullPath, m)));
|
|
23208
|
+
if (hasMarker) {
|
|
23209
|
+
results.push({
|
|
23210
|
+
suggestedId: pathToId(relPath),
|
|
23211
|
+
suggestedName: pathToName(relPath),
|
|
23212
|
+
path: relPath,
|
|
23213
|
+
type: "package-root",
|
|
23214
|
+
alreadyTracked: false
|
|
23215
|
+
});
|
|
22924
23216
|
}
|
|
22925
|
-
|
|
22926
|
-
logger.info("Fix the errors above, then run `wairon lock` again. Nothing was changed.");
|
|
22927
|
-
process.exit(1);
|
|
23217
|
+
walkForPackages(projectRoot2, fullPath, depth + 1, results);
|
|
22928
23218
|
}
|
|
22929
|
-
|
|
23219
|
+
}
|
|
23220
|
+
function pathToId(relPath) {
|
|
23221
|
+
const basename11 = path27.basename(relPath);
|
|
23222
|
+
return basename11.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
23223
|
+
}
|
|
23224
|
+
function pathToName(relPath) {
|
|
23225
|
+
const id = pathToId(relPath);
|
|
23226
|
+
return id.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
23227
|
+
}
|
|
23228
|
+
function normalizePath3(p) {
|
|
23229
|
+
return p.replace(/\\/g, "/");
|
|
23230
|
+
}
|
|
23231
|
+
|
|
23232
|
+
// src/core/index.ts
|
|
23233
|
+
init_domains();
|
|
23234
|
+
init_validation();
|
|
23235
|
+
init_extensions();
|
|
23236
|
+
init_variants();
|
|
23237
|
+
init_rules();
|
|
23238
|
+
init_specs2();
|
|
23239
|
+
init_provision();
|
|
23240
|
+
init_diagram();
|
|
23241
|
+
|
|
23242
|
+
// src/core/lockfile.ts
|
|
23243
|
+
var fs19 = __toESM(require("fs"));
|
|
23244
|
+
var path28 = __toESM(require("path"));
|
|
23245
|
+
init_fs();
|
|
23246
|
+
function lockPath() {
|
|
23247
|
+
return aiDir("lock.json");
|
|
23248
|
+
}
|
|
23249
|
+
function readLockRecord() {
|
|
23250
|
+
try {
|
|
23251
|
+
return JSON.parse(fs19.readFileSync(lockPath(), "utf8"));
|
|
23252
|
+
} catch {
|
|
23253
|
+
return null;
|
|
23254
|
+
}
|
|
23255
|
+
}
|
|
23256
|
+
function writeLockRecord(record2) {
|
|
23257
|
+
const p = lockPath();
|
|
23258
|
+
fs19.mkdirSync(path28.dirname(p), { recursive: true });
|
|
23259
|
+
const tmp = `${p}.tmp`;
|
|
23260
|
+
fs19.writeFileSync(tmp, JSON.stringify(record2, null, 2) + "\n");
|
|
23261
|
+
fs19.renameSync(tmp, p);
|
|
23262
|
+
}
|
|
23263
|
+
|
|
23264
|
+
// src/core/index.ts
|
|
23265
|
+
init_statehash();
|
|
23266
|
+
init_agent_resolver();
|
|
23267
|
+
init_skills();
|
|
23268
|
+
init_surfaces();
|
|
23269
|
+
init_openapi();
|
|
23270
|
+
|
|
23271
|
+
// src/commands/lock.ts
|
|
23272
|
+
async function runLock(options = {}, gate) {
|
|
23273
|
+
const promotable = collectPromotableSpecs(options.subsystem);
|
|
22930
23274
|
if (promotable.length === 0) {
|
|
22931
23275
|
logger.info("All specs are already complete \u2014 this will re-validate and regenerate the agent topology.");
|
|
22932
23276
|
} else {
|
|
@@ -22950,23 +23294,36 @@ async function runLock(options = {}) {
|
|
|
22950
23294
|
default: false
|
|
22951
23295
|
}
|
|
22952
23296
|
]);
|
|
22953
|
-
if (!confirmed)
|
|
22954
|
-
|
|
22955
|
-
|
|
22956
|
-
|
|
23297
|
+
if (!confirmed) return null;
|
|
23298
|
+
}
|
|
23299
|
+
if (options.subsystem) {
|
|
23300
|
+
for (const p of promotable) applySpecStatus(p.kind, p.id, "complete");
|
|
23301
|
+
invalidateSpecCache();
|
|
23302
|
+
} else {
|
|
23303
|
+
promoteAllComplete();
|
|
22957
23304
|
}
|
|
22958
|
-
for (const p of promotable) applySpecStatus(p.kind, p.id, "complete");
|
|
22959
|
-
invalidateSpecCache();
|
|
22960
23305
|
if (promotable.length > 0) {
|
|
22961
23306
|
logger.success(`Locked ${promotable.length} spec(s) as complete.`);
|
|
22962
23307
|
}
|
|
22963
|
-
|
|
22964
|
-
|
|
22965
|
-
|
|
22966
|
-
|
|
22967
|
-
|
|
22968
|
-
|
|
22969
|
-
|
|
23308
|
+
let lockedBy = "local";
|
|
23309
|
+
try {
|
|
23310
|
+
lockedBy = `local:${os7.userInfo().username}`;
|
|
23311
|
+
} catch {
|
|
23312
|
+
}
|
|
23313
|
+
const record2 = {
|
|
23314
|
+
stateId: computeStateId(),
|
|
23315
|
+
lockedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
23316
|
+
lockedBy,
|
|
23317
|
+
validatorVersion: WAIRON_VERSION,
|
|
23318
|
+
validationResult: {
|
|
23319
|
+
valid: true,
|
|
23320
|
+
errors: 0,
|
|
23321
|
+
warnings: gate ? gate.issues.filter((i) => i.severity === "warning").length : 0
|
|
23322
|
+
},
|
|
23323
|
+
status: "ready"
|
|
23324
|
+
};
|
|
23325
|
+
writeLockRecord(record2);
|
|
23326
|
+
return record2;
|
|
22970
23327
|
}
|
|
22971
23328
|
|
|
22972
23329
|
// src/commands/validate.ts
|
|
@@ -23105,6 +23462,10 @@ async function runValidate(options = {}) {
|
|
|
23105
23462
|
}
|
|
23106
23463
|
}
|
|
23107
23464
|
|
|
23465
|
+
// src/cli/index.ts
|
|
23466
|
+
init_loader();
|
|
23467
|
+
init_fs();
|
|
23468
|
+
|
|
23108
23469
|
// src/commands/list.ts
|
|
23109
23470
|
var import_chalk7 = __toESM(require("chalk"));
|
|
23110
23471
|
init_logger();
|
|
@@ -23219,9 +23580,9 @@ init_mcp();
|
|
|
23219
23580
|
// src/commands/update.ts
|
|
23220
23581
|
var https = __toESM(require("https"));
|
|
23221
23582
|
var http = __toESM(require("http"));
|
|
23222
|
-
var
|
|
23223
|
-
var
|
|
23224
|
-
var
|
|
23583
|
+
var fs20 = __toESM(require("fs"));
|
|
23584
|
+
var path29 = __toESM(require("path"));
|
|
23585
|
+
var os8 = __toESM(require("os"));
|
|
23225
23586
|
var crypto2 = __toESM(require("crypto"));
|
|
23226
23587
|
var import_child_process2 = require("child_process");
|
|
23227
23588
|
init_logger();
|
|
@@ -23284,8 +23645,8 @@ async function runUpdate(options = {}) {
|
|
|
23284
23645
|
logger.info(`Download manually from: ${release.html_url}`);
|
|
23285
23646
|
process.exit(1);
|
|
23286
23647
|
}
|
|
23287
|
-
const tmpDir =
|
|
23288
|
-
const tmpFile =
|
|
23648
|
+
const tmpDir = os8.tmpdir();
|
|
23649
|
+
const tmpFile = path29.join(tmpDir, assetName);
|
|
23289
23650
|
logger.info(`Downloading ${assetName}...`);
|
|
23290
23651
|
try {
|
|
23291
23652
|
await downloadFile(asset.browser_download_url, tmpFile);
|
|
@@ -23302,16 +23663,16 @@ async function runUpdate(options = {}) {
|
|
|
23302
23663
|
const checksumAssetName = assetName + ".sha256";
|
|
23303
23664
|
const checksumAsset = release.assets.find((a) => a.name === checksumAssetName);
|
|
23304
23665
|
if (checksumAsset) {
|
|
23305
|
-
const tmpChecksum =
|
|
23666
|
+
const tmpChecksum = path29.join(tmpDir, checksumAssetName);
|
|
23306
23667
|
logger.info(`Verifying checksum...`);
|
|
23307
23668
|
try {
|
|
23308
23669
|
await downloadFile(checksumAsset.browser_download_url, tmpChecksum);
|
|
23309
23670
|
verifyChecksum(tmpFile, tmpChecksum, assetName);
|
|
23310
|
-
|
|
23671
|
+
fs20.unlinkSync(tmpChecksum);
|
|
23311
23672
|
} catch (err) {
|
|
23312
23673
|
logger.error(`Checksum verification failed: ${err.message}`);
|
|
23313
23674
|
try {
|
|
23314
|
-
|
|
23675
|
+
fs20.unlinkSync(tmpFile);
|
|
23315
23676
|
} catch {
|
|
23316
23677
|
}
|
|
23317
23678
|
process.exit(1);
|
|
@@ -23377,7 +23738,7 @@ function fetchReleases(repo) {
|
|
|
23377
23738
|
}
|
|
23378
23739
|
function downloadFile(url, dest) {
|
|
23379
23740
|
return new Promise((resolve24, reject) => {
|
|
23380
|
-
const file =
|
|
23741
|
+
const file = fs20.createWriteStream(dest);
|
|
23381
23742
|
const get3 = url.startsWith("https://") ? https.get : http.get;
|
|
23382
23743
|
get3(url, { headers: { "User-Agent": `wairon/${WAIRON_VERSION}` }, agent: false }, (res) => {
|
|
23383
23744
|
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
@@ -23399,21 +23760,21 @@ function downloadFile(url, dest) {
|
|
|
23399
23760
|
});
|
|
23400
23761
|
file.on("error", (err) => {
|
|
23401
23762
|
res.destroy();
|
|
23402
|
-
|
|
23763
|
+
fs20.unlink(dest, () => {
|
|
23403
23764
|
});
|
|
23404
23765
|
reject(err);
|
|
23405
23766
|
});
|
|
23406
23767
|
}).on("error", (err) => {
|
|
23407
|
-
|
|
23768
|
+
fs20.unlink(dest, () => {
|
|
23408
23769
|
});
|
|
23409
23770
|
reject(err);
|
|
23410
23771
|
});
|
|
23411
23772
|
});
|
|
23412
23773
|
}
|
|
23413
23774
|
function verifyChecksum(filePath, checksumFile, expectedFilename) {
|
|
23414
|
-
const checksumContent =
|
|
23775
|
+
const checksumContent = fs20.readFileSync(checksumFile, "utf-8").trim();
|
|
23415
23776
|
const expectedHash = checksumContent.split(/\s+/)[0].toLowerCase();
|
|
23416
|
-
const fileBuffer =
|
|
23777
|
+
const fileBuffer = fs20.readFileSync(filePath);
|
|
23417
23778
|
const actualHash = crypto2.createHash("sha256").update(fileBuffer).digest("hex").toLowerCase();
|
|
23418
23779
|
if (actualHash !== expectedHash) {
|
|
23419
23780
|
throw new Error(
|
|
@@ -23444,9 +23805,9 @@ function isPkgBinary2() {
|
|
|
23444
23805
|
function installBinary(tmpFile, destPath) {
|
|
23445
23806
|
const platform = process.platform;
|
|
23446
23807
|
const isZip = tmpFile.endsWith(".zip");
|
|
23447
|
-
const extractDir =
|
|
23448
|
-
if (
|
|
23449
|
-
|
|
23808
|
+
const extractDir = path29.join(os8.tmpdir(), "wairon-extract");
|
|
23809
|
+
if (fs20.existsSync(extractDir)) fs20.rmSync(extractDir, { recursive: true });
|
|
23810
|
+
fs20.mkdirSync(extractDir, { recursive: true });
|
|
23450
23811
|
if (isZip) {
|
|
23451
23812
|
(0, import_child_process2.execSync)(
|
|
23452
23813
|
`powershell -NoProfile -NonInteractive -Command "Expand-Archive -Path '${tmpFile}' -DestinationPath '${extractDir}' -Force"`,
|
|
@@ -23456,18 +23817,18 @@ function installBinary(tmpFile, destPath) {
|
|
|
23456
23817
|
(0, import_child_process2.execSync)(`tar -xzf "${tmpFile}" -C "${extractDir}"`, { stdio: ["ignore", "pipe", "pipe"] });
|
|
23457
23818
|
}
|
|
23458
23819
|
const binaryName = platform === "win32" ? "wairon.exe" : "wairon";
|
|
23459
|
-
const extractedBinary =
|
|
23460
|
-
if (!
|
|
23820
|
+
const extractedBinary = path29.join(extractDir, binaryName);
|
|
23821
|
+
if (!fs20.existsSync(extractedBinary)) {
|
|
23461
23822
|
throw new Error(`Extracted binary not found at ${extractedBinary}`);
|
|
23462
23823
|
}
|
|
23463
23824
|
if (platform === "win32") {
|
|
23464
23825
|
const oldPath = destPath + ".old";
|
|
23465
23826
|
try {
|
|
23466
23827
|
cleanStaleBinary(oldPath);
|
|
23467
|
-
|
|
23468
|
-
|
|
23828
|
+
fs20.renameSync(destPath, oldPath);
|
|
23829
|
+
fs20.copyFileSync(extractedBinary, destPath);
|
|
23469
23830
|
try {
|
|
23470
|
-
|
|
23831
|
+
fs20.unlinkSync(oldPath);
|
|
23471
23832
|
} catch {
|
|
23472
23833
|
}
|
|
23473
23834
|
} catch (err) {
|
|
@@ -23481,25 +23842,25 @@ function installBinary(tmpFile, destPath) {
|
|
|
23481
23842
|
}
|
|
23482
23843
|
} else {
|
|
23483
23844
|
const tmpDest = destPath + ".new";
|
|
23484
|
-
|
|
23485
|
-
|
|
23486
|
-
|
|
23845
|
+
fs20.copyFileSync(extractedBinary, tmpDest);
|
|
23846
|
+
fs20.chmodSync(tmpDest, 493);
|
|
23847
|
+
fs20.renameSync(tmpDest, destPath);
|
|
23487
23848
|
}
|
|
23488
23849
|
try {
|
|
23489
|
-
|
|
23850
|
+
fs20.unlinkSync(tmpFile);
|
|
23490
23851
|
} catch {
|
|
23491
23852
|
}
|
|
23492
23853
|
try {
|
|
23493
|
-
|
|
23854
|
+
fs20.rmSync(extractDir, { recursive: true });
|
|
23494
23855
|
} catch {
|
|
23495
23856
|
}
|
|
23496
23857
|
}
|
|
23497
23858
|
function cleanStaleBinary(oldPath) {
|
|
23498
23859
|
const target = oldPath ?? (isPkgBinary2() ? process.execPath + ".old" : null);
|
|
23499
23860
|
if (!target) return;
|
|
23500
|
-
if (
|
|
23861
|
+
if (fs20.existsSync(target)) {
|
|
23501
23862
|
try {
|
|
23502
|
-
|
|
23863
|
+
fs20.unlinkSync(target);
|
|
23503
23864
|
} catch {
|
|
23504
23865
|
}
|
|
23505
23866
|
}
|
|
@@ -23745,171 +24106,6 @@ async function filteredCheckbox(config) {
|
|
|
23745
24106
|
|
|
23746
24107
|
// src/commands/domains.ts
|
|
23747
24108
|
init_loader();
|
|
23748
|
-
|
|
23749
|
-
// src/core/detection.ts
|
|
23750
|
-
var fs19 = __toESM(require("fs"));
|
|
23751
|
-
var path28 = __toESM(require("path"));
|
|
23752
|
-
init_defaults();
|
|
23753
|
-
var PACKAGE_MARKERS = [
|
|
23754
|
-
"package.json",
|
|
23755
|
-
"pyproject.toml",
|
|
23756
|
-
"Cargo.toml",
|
|
23757
|
-
"go.mod",
|
|
23758
|
-
"build.gradle",
|
|
23759
|
-
"build.gradle.kts",
|
|
23760
|
-
"pom.xml"
|
|
23761
|
-
];
|
|
23762
|
-
var MAX_SCAN_DEPTH = 5;
|
|
23763
|
-
function detectDomainCandidates(projectRoot2, alreadyTrackedPaths = /* @__PURE__ */ new Set(), alreadyTrackedIds = /* @__PURE__ */ new Set()) {
|
|
23764
|
-
const candidates = /* @__PURE__ */ new Map();
|
|
23765
|
-
for (const c of detectGitSubmodules(projectRoot2)) {
|
|
23766
|
-
candidates.set(c.path, { ...c, alreadyTracked: alreadyTrackedPaths.has(c.path) });
|
|
23767
|
-
}
|
|
23768
|
-
for (const c of detectNestedGitRepos(projectRoot2)) {
|
|
23769
|
-
if (!candidates.has(c.path)) {
|
|
23770
|
-
candidates.set(c.path, { ...c, alreadyTracked: alreadyTrackedPaths.has(c.path) });
|
|
23771
|
-
}
|
|
23772
|
-
}
|
|
23773
|
-
const gitPaths = new Set(
|
|
23774
|
-
Array.from(candidates.values()).filter((c) => c.type === "git-submodule" || c.type === "git-repo").map((c) => c.path)
|
|
23775
|
-
);
|
|
23776
|
-
for (const c of detectPackageRoots(projectRoot2)) {
|
|
23777
|
-
if (candidates.has(c.path)) continue;
|
|
23778
|
-
const insideGit = Array.from(gitPaths).some(
|
|
23779
|
-
(gp) => c.path === gp || c.path.startsWith(gp + "/")
|
|
23780
|
-
);
|
|
23781
|
-
if (insideGit) continue;
|
|
23782
|
-
candidates.set(c.path, { ...c, alreadyTracked: alreadyTrackedPaths.has(c.path) });
|
|
23783
|
-
}
|
|
23784
|
-
const sorted = Array.from(candidates.values()).sort((a, b) => a.path.localeCompare(b.path));
|
|
23785
|
-
return deduplicateIds(sorted, alreadyTrackedIds);
|
|
23786
|
-
}
|
|
23787
|
-
function deduplicateIds(candidates, existingIds = /* @__PURE__ */ new Set()) {
|
|
23788
|
-
const idCount = /* @__PURE__ */ new Map();
|
|
23789
|
-
for (const id of existingIds) {
|
|
23790
|
-
idCount.set(id, (idCount.get(id) ?? 0) + 1);
|
|
23791
|
-
}
|
|
23792
|
-
for (const c of candidates) {
|
|
23793
|
-
idCount.set(c.suggestedId, (idCount.get(c.suggestedId) ?? 0) + 1);
|
|
23794
|
-
}
|
|
23795
|
-
return candidates.map((c) => {
|
|
23796
|
-
if ((idCount.get(c.suggestedId) ?? 0) <= 1) return c;
|
|
23797
|
-
const parts = c.path.split("/");
|
|
23798
|
-
const qualifiedId2 = parts.length >= 2 ? pathToId(`${parts[parts.length - 2]}-${parts[parts.length - 1]}`) : c.suggestedId;
|
|
23799
|
-
return { ...c, suggestedId: qualifiedId2 };
|
|
23800
|
-
});
|
|
23801
|
-
}
|
|
23802
|
-
function parseGitmodules(filePath) {
|
|
23803
|
-
const content = fs19.readFileSync(filePath, "utf-8");
|
|
23804
|
-
const entries = [];
|
|
23805
|
-
let current2 = {};
|
|
23806
|
-
for (const line2 of content.split("\n")) {
|
|
23807
|
-
const trimmed = line2.trim();
|
|
23808
|
-
const headerMatch = trimmed.match(/^\[submodule "(.+)"\]$/);
|
|
23809
|
-
if (headerMatch) {
|
|
23810
|
-
if (current2.path) entries.push(current2);
|
|
23811
|
-
current2 = { name: headerMatch[1] };
|
|
23812
|
-
continue;
|
|
23813
|
-
}
|
|
23814
|
-
const keyVal = trimmed.match(/^(\w+)\s*=\s*(.+)$/);
|
|
23815
|
-
if (keyVal) {
|
|
23816
|
-
const [, key, value] = keyVal;
|
|
23817
|
-
if (key === "path") current2.path = value.trim();
|
|
23818
|
-
if (key === "url") current2.url = value.trim();
|
|
23819
|
-
}
|
|
23820
|
-
}
|
|
23821
|
-
if (current2.path) entries.push(current2);
|
|
23822
|
-
return entries;
|
|
23823
|
-
}
|
|
23824
|
-
function detectGitSubmodules(projectRoot2) {
|
|
23825
|
-
const gitmodulesPath = path28.join(projectRoot2, ".gitmodules");
|
|
23826
|
-
if (!fs19.existsSync(gitmodulesPath)) return [];
|
|
23827
|
-
return parseGitmodules(gitmodulesPath).map((entry) => ({
|
|
23828
|
-
suggestedId: pathToId(entry.path),
|
|
23829
|
-
suggestedName: pathToName(entry.path),
|
|
23830
|
-
path: normalizePath3(entry.path),
|
|
23831
|
-
type: "git-submodule",
|
|
23832
|
-
alreadyTracked: false
|
|
23833
|
-
}));
|
|
23834
|
-
}
|
|
23835
|
-
function detectNestedGitRepos(projectRoot2) {
|
|
23836
|
-
const results = [];
|
|
23837
|
-
walkForGit(projectRoot2, projectRoot2, 0, results);
|
|
23838
|
-
return results;
|
|
23839
|
-
}
|
|
23840
|
-
function walkForGit(projectRoot2, currentDir, depth, results) {
|
|
23841
|
-
if (depth > MAX_SCAN_DEPTH) return;
|
|
23842
|
-
let entries;
|
|
23843
|
-
try {
|
|
23844
|
-
entries = fs19.readdirSync(currentDir, { withFileTypes: true });
|
|
23845
|
-
} catch {
|
|
23846
|
-
return;
|
|
23847
|
-
}
|
|
23848
|
-
for (const entry of entries) {
|
|
23849
|
-
if (!entry.isDirectory()) continue;
|
|
23850
|
-
if (SCAN_EXCLUDE_DIRS.has(entry.name)) continue;
|
|
23851
|
-
const fullPath = path28.join(currentDir, entry.name);
|
|
23852
|
-
const relPath = normalizePath3(path28.relative(projectRoot2, fullPath));
|
|
23853
|
-
if (relPath === "" || relPath === ".") continue;
|
|
23854
|
-
const gitPath = path28.join(fullPath, ".git");
|
|
23855
|
-
if (fs19.existsSync(gitPath)) {
|
|
23856
|
-
results.push({
|
|
23857
|
-
suggestedId: pathToId(relPath),
|
|
23858
|
-
suggestedName: pathToName(relPath),
|
|
23859
|
-
path: relPath,
|
|
23860
|
-
type: "git-repo",
|
|
23861
|
-
alreadyTracked: false
|
|
23862
|
-
});
|
|
23863
|
-
continue;
|
|
23864
|
-
}
|
|
23865
|
-
walkForGit(projectRoot2, fullPath, depth + 1, results);
|
|
23866
|
-
}
|
|
23867
|
-
}
|
|
23868
|
-
function detectPackageRoots(projectRoot2) {
|
|
23869
|
-
const results = [];
|
|
23870
|
-
walkForPackages(projectRoot2, projectRoot2, 0, results);
|
|
23871
|
-
return results;
|
|
23872
|
-
}
|
|
23873
|
-
function walkForPackages(projectRoot2, currentDir, depth, results) {
|
|
23874
|
-
if (depth > MAX_SCAN_DEPTH) return;
|
|
23875
|
-
let entries;
|
|
23876
|
-
try {
|
|
23877
|
-
entries = fs19.readdirSync(currentDir, { withFileTypes: true });
|
|
23878
|
-
} catch {
|
|
23879
|
-
return;
|
|
23880
|
-
}
|
|
23881
|
-
for (const entry of entries) {
|
|
23882
|
-
if (!entry.isDirectory()) continue;
|
|
23883
|
-
if (SCAN_EXCLUDE_DIRS.has(entry.name)) continue;
|
|
23884
|
-
const fullPath = path28.join(currentDir, entry.name);
|
|
23885
|
-
const relPath = normalizePath3(path28.relative(projectRoot2, fullPath));
|
|
23886
|
-
if (relPath === "" || relPath === ".") continue;
|
|
23887
|
-
const hasMarker = PACKAGE_MARKERS.some((m) => fs19.existsSync(path28.join(fullPath, m)));
|
|
23888
|
-
if (hasMarker) {
|
|
23889
|
-
results.push({
|
|
23890
|
-
suggestedId: pathToId(relPath),
|
|
23891
|
-
suggestedName: pathToName(relPath),
|
|
23892
|
-
path: relPath,
|
|
23893
|
-
type: "package-root",
|
|
23894
|
-
alreadyTracked: false
|
|
23895
|
-
});
|
|
23896
|
-
}
|
|
23897
|
-
walkForPackages(projectRoot2, fullPath, depth + 1, results);
|
|
23898
|
-
}
|
|
23899
|
-
}
|
|
23900
|
-
function pathToId(relPath) {
|
|
23901
|
-
const basename12 = path28.basename(relPath);
|
|
23902
|
-
return basename12.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
23903
|
-
}
|
|
23904
|
-
function pathToName(relPath) {
|
|
23905
|
-
const id = pathToId(relPath);
|
|
23906
|
-
return id.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
23907
|
-
}
|
|
23908
|
-
function normalizePath3(p) {
|
|
23909
|
-
return p.replace(/\\/g, "/");
|
|
23910
|
-
}
|
|
23911
|
-
|
|
23912
|
-
// src/commands/domains.ts
|
|
23913
24109
|
init_domains();
|
|
23914
24110
|
init_domain();
|
|
23915
24111
|
async function runDomainsList() {
|
|
@@ -24099,9 +24295,9 @@ async function runSkillsInstall() {
|
|
|
24099
24295
|
}
|
|
24100
24296
|
|
|
24101
24297
|
// src/commands/doctor.ts
|
|
24102
|
-
var
|
|
24103
|
-
var
|
|
24104
|
-
var
|
|
24298
|
+
var fs21 = __toESM(require("fs"));
|
|
24299
|
+
var os9 = __toESM(require("os"));
|
|
24300
|
+
var path30 = __toESM(require("path"));
|
|
24105
24301
|
var import_chalk12 = __toESM(require("chalk"));
|
|
24106
24302
|
init_logger();
|
|
24107
24303
|
init_defaults();
|
|
@@ -24131,10 +24327,10 @@ function stampVerdict(content) {
|
|
|
24131
24327
|
return { mark: "warn", note: `v${v} \u2014 stale, installed is v${WAIRON_VERSION}` };
|
|
24132
24328
|
}
|
|
24133
24329
|
function mcpEntryHealth(settingsPath) {
|
|
24134
|
-
if (!
|
|
24330
|
+
if (!fs21.existsSync(settingsPath)) return { mark: "warn", note: "not registered" };
|
|
24135
24331
|
let entry;
|
|
24136
24332
|
try {
|
|
24137
|
-
const s = JSON.parse(
|
|
24333
|
+
const s = JSON.parse(fs21.readFileSync(settingsPath, "utf8"));
|
|
24138
24334
|
entry = s.mcpServers?.["wairon"];
|
|
24139
24335
|
} catch {
|
|
24140
24336
|
return { mark: "error", note: "parse error" };
|
|
@@ -24142,7 +24338,7 @@ function mcpEntryHealth(settingsPath) {
|
|
|
24142
24338
|
if (!entry) return { mark: "warn", note: "not registered" };
|
|
24143
24339
|
if (entry.command === "node" && Array.isArray(entry.args) && typeof entry.args[0] === "string") {
|
|
24144
24340
|
const scriptPath = entry.args[0];
|
|
24145
|
-
if (!
|
|
24341
|
+
if (!fs21.existsSync(scriptPath)) {
|
|
24146
24342
|
return { mark: "error", note: `registered but the server path is missing \u2014 ${scriptPath}` };
|
|
24147
24343
|
}
|
|
24148
24344
|
}
|
|
@@ -24194,7 +24390,7 @@ async function runDoctor(options = {}) {
|
|
|
24194
24390
|
const { findChainingSubprojectsMissingConfig: findChainingSubprojectsMissingConfig2 } = (init_provision(), __toCommonJS(provision_exports));
|
|
24195
24391
|
const missing = findChainingSubprojectsMissingConfig2(getProjectRoot());
|
|
24196
24392
|
if (missing.length > 0) {
|
|
24197
|
-
line(tally, "warn", `${missing.length} chained subproject(s) have specs but no project.yaml (un-runnable standalone): ${missing.map((d) =>
|
|
24393
|
+
line(tally, "warn", `${missing.length} chained subproject(s) have specs but no project.yaml (un-runnable standalone): ${missing.map((d) => path30.relative(getProjectRoot(), d) || ".").join(", ")}. Run \`wairon doctor --fix\` to initialize them.`);
|
|
24198
24394
|
}
|
|
24199
24395
|
} catch {
|
|
24200
24396
|
}
|
|
@@ -24232,7 +24428,7 @@ async function runDoctor(options = {}) {
|
|
|
24232
24428
|
const gp = localGuideFilePath(process.cwd(), t);
|
|
24233
24429
|
if (!gp || seenGuides.has(gp)) continue;
|
|
24234
24430
|
seenGuides.add(gp);
|
|
24235
|
-
const rel2 =
|
|
24431
|
+
const rel2 = path30.relative(process.cwd(), gp).replace(/\\/g, "/");
|
|
24236
24432
|
if (!pathExists(gp)) {
|
|
24237
24433
|
line(tally, "warn", `${rel2} guide \u2014 not injected (run \`wairon generate\`)`);
|
|
24238
24434
|
continue;
|
|
@@ -24266,17 +24462,17 @@ async function runDoctor(options = {}) {
|
|
|
24266
24462
|
line(tally, h.mark, `Claude (project .mcp.json): ${h.note}${h.mark === "ok" ? "" : " \u2014 run `wairon mcp install --backend claude`"}`);
|
|
24267
24463
|
}
|
|
24268
24464
|
if (wantGemini) {
|
|
24269
|
-
const globalCfg =
|
|
24465
|
+
const globalCfg = path30.join(os9.homedir(), ".gemini", "antigravity-cli", "mcp_config.json");
|
|
24270
24466
|
const hg = mcpEntryHealth(globalCfg);
|
|
24271
24467
|
line(tally, hg.mark, `Antigravity (global mcp_config.json): ${hg.note}${hg.mark === "ok" ? "" : " \u2014 run `wairon mcp install --backend gemini --global`"}`);
|
|
24272
24468
|
const projPath = fromProjectRoot(".gemini", "settings.json");
|
|
24273
|
-
if (
|
|
24469
|
+
if (fs21.existsSync(projPath)) {
|
|
24274
24470
|
const hp = mcpEntryHealth(projPath);
|
|
24275
24471
|
line(tally, hp.mark === "error" ? "error" : "ok", `Gemini CLI (project): ${hp.note} ${import_chalk12.default.gray("(Antigravity ignores this file)")}`);
|
|
24276
24472
|
}
|
|
24277
24473
|
}
|
|
24278
|
-
const pluginDir =
|
|
24279
|
-
if (
|
|
24474
|
+
const pluginDir = path30.join(os9.homedir(), ".gemini", "config", "plugins", "wairon");
|
|
24475
|
+
if (fs21.existsSync(pluginDir)) {
|
|
24280
24476
|
line(tally, "warn", `Legacy Antigravity plugin present (${pluginDir}) \u2014 it collides with the wairon MCP server. Remove it with \`wairon doctor --fix\`.`);
|
|
24281
24477
|
}
|
|
24282
24478
|
logger.blank();
|
|
@@ -24307,7 +24503,7 @@ async function applyFixes() {
|
|
|
24307
24503
|
const legacySpecs = findLegacySpecFiles();
|
|
24308
24504
|
if (legacySpecs.length > 0) {
|
|
24309
24505
|
for (const { path: oldPath, expected: newPath } of legacySpecs) {
|
|
24310
|
-
|
|
24506
|
+
fs21.renameSync(oldPath, newPath);
|
|
24311
24507
|
}
|
|
24312
24508
|
console.log(` ${icon("ok")} Migrated ${legacySpecs.length} legacy spec file(s) to the new dot-prefixed unified schema.`);
|
|
24313
24509
|
}
|
|
@@ -24361,8 +24557,8 @@ function printSummary(tally) {
|
|
|
24361
24557
|
}
|
|
24362
24558
|
|
|
24363
24559
|
// src/commands/diagram.ts
|
|
24364
|
-
var
|
|
24365
|
-
var
|
|
24560
|
+
var fs22 = __toESM(require("fs"));
|
|
24561
|
+
var path31 = __toESM(require("path"));
|
|
24366
24562
|
init_logger();
|
|
24367
24563
|
init_loader();
|
|
24368
24564
|
init_fs();
|
|
@@ -24397,8 +24593,8 @@ function collectIssues() {
|
|
|
24397
24593
|
}
|
|
24398
24594
|
function writeCanvas(dest) {
|
|
24399
24595
|
const model = buildCanvasModel(collectIssues());
|
|
24400
|
-
ensureDir(
|
|
24401
|
-
|
|
24596
|
+
ensureDir(path31.dirname(path31.resolve(dest)));
|
|
24597
|
+
fs22.writeFileSync(dest, renderCanvasHtml(model), "utf-8");
|
|
24402
24598
|
}
|
|
24403
24599
|
function parseSequenceRef(ref) {
|
|
24404
24600
|
const sep6 = ref.includes(":") ? ref.lastIndexOf(":") : ref.lastIndexOf(".");
|
|
@@ -24413,55 +24609,55 @@ async function runDiagram(rawOptions = {}) {
|
|
|
24413
24609
|
assertProjectInitialized();
|
|
24414
24610
|
const options = applyFormat(rawOptions);
|
|
24415
24611
|
if (options.canvas && !options.all) {
|
|
24416
|
-
const dest2 = options.out ??
|
|
24612
|
+
const dest2 = options.out ?? path31.join(AI_PATHS.docsDir(), "diagrams", "canvas.html");
|
|
24417
24613
|
writeCanvas(dest2);
|
|
24418
24614
|
logger.success(`Interactive canvas written to ${dest2}`);
|
|
24419
24615
|
logger.info("Open it in a browser \u2014 fully self-contained (works offline).");
|
|
24420
24616
|
return;
|
|
24421
24617
|
}
|
|
24422
24618
|
if (options.drawio && !options.all) {
|
|
24423
|
-
const dest2 = options.out ??
|
|
24424
|
-
ensureDir(
|
|
24425
|
-
|
|
24619
|
+
const dest2 = options.out ?? path31.join(AI_PATHS.docsDir(), "diagrams", "architecture.drawio");
|
|
24620
|
+
ensureDir(path31.dirname(path31.resolve(dest2)));
|
|
24621
|
+
fs22.writeFileSync(dest2, generateDrawioXml(buildCanvasModel()), "utf-8");
|
|
24426
24622
|
logger.success(`draw.io diagram written to ${dest2}`);
|
|
24427
24623
|
logger.info("Open with draw.io / diagrams.net (or import into tools that accept the format).");
|
|
24428
24624
|
return;
|
|
24429
24625
|
}
|
|
24430
24626
|
if (options.excalidraw && !options.all) {
|
|
24431
|
-
const dest2 = options.out ??
|
|
24432
|
-
ensureDir(
|
|
24433
|
-
|
|
24627
|
+
const dest2 = options.out ?? path31.join(AI_PATHS.docsDir(), "diagrams", "architecture.excalidraw");
|
|
24628
|
+
ensureDir(path31.dirname(path31.resolve(dest2)));
|
|
24629
|
+
fs22.writeFileSync(dest2, generateExcalidrawScene(buildCanvasModel()), "utf-8");
|
|
24434
24630
|
logger.success(`Excalidraw scene written to ${dest2}`);
|
|
24435
24631
|
logger.info("Open with excalidraw.com or the VS Code extension.");
|
|
24436
24632
|
return;
|
|
24437
24633
|
}
|
|
24438
24634
|
const wantsMermaid = options.format?.toLowerCase().startsWith("mermaid") || !!options.subsystem || !!options.sequence;
|
|
24439
24635
|
if (!options.all && !options.sequence && !wantsMermaid) {
|
|
24440
|
-
const dest2 = options.out ??
|
|
24636
|
+
const dest2 = options.out ?? path31.join(AI_PATHS.docsDir(), "diagrams", "canvas.html");
|
|
24441
24637
|
writeCanvas(dest2);
|
|
24442
24638
|
logger.success(`Interactive canvas written to ${dest2}`);
|
|
24443
24639
|
logger.info("Open it in a browser \u2014 fully self-contained (works offline). Other formats: --format mermaid|drawio|excalidraw.");
|
|
24444
24640
|
return;
|
|
24445
24641
|
}
|
|
24446
24642
|
if (options.all) {
|
|
24447
|
-
const outDir = options.out ??
|
|
24643
|
+
const outDir = options.out ?? path31.join(AI_PATHS.docsDir(), "diagrams");
|
|
24448
24644
|
const files = generateDiagramSet();
|
|
24449
24645
|
if (files.length === 0) {
|
|
24450
24646
|
logger.warn("No diagrams to generate \u2014 the spec tree has no components yet.");
|
|
24451
24647
|
return;
|
|
24452
24648
|
}
|
|
24453
24649
|
for (const file of files) {
|
|
24454
|
-
const dest2 =
|
|
24455
|
-
ensureDir(
|
|
24456
|
-
|
|
24650
|
+
const dest2 = path31.join(outDir, file.relPath);
|
|
24651
|
+
ensureDir(path31.dirname(dest2));
|
|
24652
|
+
fs22.writeFileSync(dest2, toMarkdown(file), "utf-8");
|
|
24457
24653
|
}
|
|
24458
|
-
writeCanvas(
|
|
24654
|
+
writeCanvas(path31.join(outDir, "canvas.html"));
|
|
24459
24655
|
const exportModel = buildCanvasModel();
|
|
24460
|
-
|
|
24461
|
-
|
|
24656
|
+
fs22.writeFileSync(path31.join(outDir, "architecture.drawio"), generateDrawioXml(exportModel), "utf-8");
|
|
24657
|
+
fs22.writeFileSync(path31.join(outDir, "architecture.excalidraw"), generateExcalidrawScene(exportModel), "utf-8");
|
|
24462
24658
|
const graph = loadSpecGraph();
|
|
24463
|
-
const indexPath =
|
|
24464
|
-
|
|
24659
|
+
const indexPath = path31.join(outDir, "README.md");
|
|
24660
|
+
fs22.writeFileSync(indexPath, diagramSetIndex(files, graph.systemName), "utf-8");
|
|
24465
24661
|
logger.success(`Generated ${files.length} diagram(s) + interactive canvas.html + index into ${outDir}`);
|
|
24466
24662
|
for (const file of files.slice(0, 12)) {
|
|
24467
24663
|
logger.info(` ${file.relPath}`);
|
|
@@ -24472,26 +24668,26 @@ async function runDiagram(rawOptions = {}) {
|
|
|
24472
24668
|
let mermaid;
|
|
24473
24669
|
let title;
|
|
24474
24670
|
let defaultDest;
|
|
24475
|
-
const diagramsDir =
|
|
24671
|
+
const diagramsDir = path31.join(AI_PATHS.docsDir(), "diagrams");
|
|
24476
24672
|
if (options.sequence) {
|
|
24477
24673
|
const { component, method: method2 } = parseSequenceRef(options.sequence);
|
|
24478
24674
|
mermaid = generateSequenceDiagram(component, method2, { depth: options.depth });
|
|
24479
24675
|
title = `${component}.${method2} \u2014 narrative sequence`;
|
|
24480
|
-
defaultDest =
|
|
24676
|
+
defaultDest = path31.join(diagramsDir, "sequences", `${component.replace(/::/g, "--")}.${method2}.md`);
|
|
24481
24677
|
} else if (options.subsystem) {
|
|
24482
24678
|
mermaid = generateComponentDiagram({ subsystem: options.subsystem });
|
|
24483
24679
|
title = `${options.subsystem} \u2014 components`;
|
|
24484
|
-
defaultDest =
|
|
24680
|
+
defaultDest = path31.join(diagramsDir, "subsystems", `${options.subsystem.replace(/::/g, "--")}.md`);
|
|
24485
24681
|
} else {
|
|
24486
24682
|
mermaid = generateComponentDiagram();
|
|
24487
24683
|
title = "Component architecture";
|
|
24488
|
-
defaultDest =
|
|
24684
|
+
defaultDest = path31.join(diagramsDir, "system.md");
|
|
24489
24685
|
}
|
|
24490
24686
|
const dest = options.out ?? defaultDest;
|
|
24491
|
-
ensureDir(
|
|
24687
|
+
ensureDir(path31.dirname(path31.resolve(dest)));
|
|
24492
24688
|
const content = dest.endsWith(".mmd") ? `${mermaid}
|
|
24493
24689
|
` : toMarkdown({ relPath: dest, title, mermaid });
|
|
24494
|
-
|
|
24690
|
+
fs22.writeFileSync(dest, content, "utf-8");
|
|
24495
24691
|
logger.success(`Mermaid diagram written to ${dest}`);
|
|
24496
24692
|
logger.info("Renders on GitHub/IDE previews; use a .mmd --out path for raw Mermaid.");
|
|
24497
24693
|
}
|
|
@@ -24600,8 +24796,8 @@ Component variants (${variants.length})
|
|
|
24600
24796
|
}
|
|
24601
24797
|
|
|
24602
24798
|
// src/commands/packs.ts
|
|
24603
|
-
var
|
|
24604
|
-
var
|
|
24799
|
+
var fs23 = __toESM(require("fs"));
|
|
24800
|
+
var path32 = __toESM(require("path"));
|
|
24605
24801
|
var import_chalk16 = __toESM(require("chalk"));
|
|
24606
24802
|
var import_sdk = __toESM(require_dist());
|
|
24607
24803
|
init_logger();
|
|
@@ -24627,11 +24823,11 @@ function describe(probe2) {
|
|
|
24627
24823
|
return parts.join(", ");
|
|
24628
24824
|
}
|
|
24629
24825
|
function resolveSourceUnit(source) {
|
|
24630
|
-
const abs =
|
|
24631
|
-
if (!
|
|
24826
|
+
const abs = path32.resolve(source);
|
|
24827
|
+
if (!fs23.existsSync(abs)) {
|
|
24632
24828
|
throw new Error(`Pack source "${source}" does not exist.`);
|
|
24633
24829
|
}
|
|
24634
|
-
const isDir =
|
|
24830
|
+
const isDir = fs23.statSync(abs).isDirectory();
|
|
24635
24831
|
if (isDir && !packDirEntry(abs)) {
|
|
24636
24832
|
throw new Error(`"${source}" is a directory without a pack entry file (pack.yaml | pack.cjs | index.cjs | ...).`);
|
|
24637
24833
|
}
|
|
@@ -24647,7 +24843,7 @@ async function addPack(source, options = {}) {
|
|
|
24647
24843
|
}
|
|
24648
24844
|
const { abs } = resolveSourceUnit(source);
|
|
24649
24845
|
const scope = options.global ? "global" : "project";
|
|
24650
|
-
const probe2 = probePack(abs,
|
|
24846
|
+
const probe2 = probePack(abs, path32.dirname(abs), scope);
|
|
24651
24847
|
if (probe2.error) {
|
|
24652
24848
|
logger.error(probe2.error);
|
|
24653
24849
|
process.exitCode = 1;
|
|
@@ -24655,10 +24851,10 @@ async function addPack(source, options = {}) {
|
|
|
24655
24851
|
}
|
|
24656
24852
|
if (options.global) {
|
|
24657
24853
|
const destDir = globalPacksDir();
|
|
24658
|
-
const dest2 =
|
|
24659
|
-
if (
|
|
24660
|
-
|
|
24661
|
-
|
|
24854
|
+
const dest2 = path32.join(destDir, path32.basename(abs));
|
|
24855
|
+
if (path32.resolve(dest2) !== abs) {
|
|
24856
|
+
fs23.mkdirSync(destDir, { recursive: true });
|
|
24857
|
+
fs23.cpSync(abs, dest2, { recursive: true, force: true });
|
|
24662
24858
|
}
|
|
24663
24859
|
logger.success(`Installed pack "${probe2.name}" globally: ${dest2}`);
|
|
24664
24860
|
logger.info(`${describe(probe2)} \u2014 auto-loaded for every project on this machine (WAIRON_PACKS_DIR / ~/.wairon/packs).`);
|
|
@@ -24671,11 +24867,11 @@ async function addPack(source, options = {}) {
|
|
|
24671
24867
|
return;
|
|
24672
24868
|
}
|
|
24673
24869
|
const root = getProjectRoot();
|
|
24674
|
-
const relRef = `.wai/packs/${
|
|
24675
|
-
const dest =
|
|
24676
|
-
if (
|
|
24677
|
-
|
|
24678
|
-
|
|
24870
|
+
const relRef = `.wai/packs/${path32.basename(abs)}`;
|
|
24871
|
+
const dest = path32.join(root, ".wai", "packs", path32.basename(abs));
|
|
24872
|
+
if (path32.resolve(dest) !== abs) {
|
|
24873
|
+
fs23.mkdirSync(path32.dirname(dest), { recursive: true });
|
|
24874
|
+
fs23.cpSync(abs, dest, { recursive: true, force: true });
|
|
24679
24875
|
}
|
|
24680
24876
|
const config = loadProjectConfig();
|
|
24681
24877
|
const packs = config.extensions?.packs ?? [];
|
|
@@ -24684,14 +24880,14 @@ async function addPack(source, options = {}) {
|
|
|
24684
24880
|
saveProjectConfig(config);
|
|
24685
24881
|
logger.success(`Vendored pack "${probe2.name}" into ${relRef} and registered it in .wai/project.yaml.`);
|
|
24686
24882
|
} else {
|
|
24687
|
-
|
|
24883
|
+
fs23.cpSync(abs, dest, { recursive: true, force: true });
|
|
24688
24884
|
logger.success(`Pack "${probe2.name}" already registered \u2014 refreshed ${relRef} from the source.`);
|
|
24689
24885
|
}
|
|
24690
24886
|
logger.info(`${describe(probe2)} \u2014 commit .wai/ so CI and every clone enforce it.`);
|
|
24691
24887
|
}
|
|
24692
24888
|
async function addPackFromArchive(source, options) {
|
|
24693
|
-
const abs =
|
|
24694
|
-
if (!
|
|
24889
|
+
const abs = path32.resolve(source);
|
|
24890
|
+
if (!fs23.existsSync(abs) || !fs23.statSync(abs).isFile()) {
|
|
24695
24891
|
logger.error(`Pack archive "${source}" does not exist.`);
|
|
24696
24892
|
process.exitCode = 1;
|
|
24697
24893
|
return;
|
|
@@ -24706,27 +24902,27 @@ async function addPackFromArchive(source, options) {
|
|
|
24706
24902
|
process.exitCode = 1;
|
|
24707
24903
|
return;
|
|
24708
24904
|
}
|
|
24709
|
-
baseDir =
|
|
24905
|
+
baseDir = path32.join(getProjectRoot(), ".wai", "packs");
|
|
24710
24906
|
}
|
|
24711
|
-
const bytes =
|
|
24712
|
-
|
|
24713
|
-
const staging =
|
|
24907
|
+
const bytes = fs23.readFileSync(abs);
|
|
24908
|
+
fs23.mkdirSync(baseDir, { recursive: true });
|
|
24909
|
+
const staging = fs23.mkdtempSync(path32.join(baseDir, ".wpack-staging-"));
|
|
24714
24910
|
let result;
|
|
24715
24911
|
try {
|
|
24716
24912
|
result = (0, import_sdk.extractPack)(bytes, staging);
|
|
24717
24913
|
} catch (err) {
|
|
24718
|
-
|
|
24719
|
-
logger.error(`Failed to extract pack archive "${
|
|
24914
|
+
fs23.rmSync(staging, { recursive: true, force: true });
|
|
24915
|
+
logger.error(`Failed to extract pack archive "${path32.basename(abs)}": ${err instanceof Error ? err.message : String(err)}`);
|
|
24720
24916
|
process.exitCode = 1;
|
|
24721
24917
|
return;
|
|
24722
24918
|
}
|
|
24723
24919
|
const name = result.name;
|
|
24724
|
-
const destDir =
|
|
24725
|
-
if (
|
|
24726
|
-
|
|
24727
|
-
const probe2 = probePack(destDir,
|
|
24920
|
+
const destDir = path32.join(baseDir, name);
|
|
24921
|
+
if (fs23.existsSync(destDir)) fs23.rmSync(destDir, { recursive: true, force: true });
|
|
24922
|
+
fs23.renameSync(staging, destDir);
|
|
24923
|
+
const probe2 = probePack(destDir, path32.dirname(destDir), scope);
|
|
24728
24924
|
if (probe2.error) {
|
|
24729
|
-
|
|
24925
|
+
fs23.rmSync(destDir, { recursive: true, force: true });
|
|
24730
24926
|
logger.error(probe2.error);
|
|
24731
24927
|
process.exitCode = 1;
|
|
24732
24928
|
return;
|
|
@@ -24744,7 +24940,7 @@ async function addPackFromArchive(source, options) {
|
|
|
24744
24940
|
saveProjectConfig(config);
|
|
24745
24941
|
logger.success(`Installed pack "${probe2.name ?? name}" into ${relRef} and registered it in .wai/project.yaml.`);
|
|
24746
24942
|
} else {
|
|
24747
|
-
logger.success(`Pack "${probe2.name ?? name}" already registered \u2014 refreshed ${relRef} from ${
|
|
24943
|
+
logger.success(`Pack "${probe2.name ?? name}" already registered \u2014 refreshed ${relRef} from ${path32.basename(abs)}.`);
|
|
24748
24944
|
}
|
|
24749
24945
|
logger.info(`${describe(probe2)} \u2014 commit .wai/ so CI and every clone enforce it.`);
|
|
24750
24946
|
}
|
|
@@ -24765,7 +24961,7 @@ async function buildPack(source, options = {}) {
|
|
|
24765
24961
|
const sourceDir = source && source.length > 0 ? source : ".";
|
|
24766
24962
|
const result = (0, import_sdk.buildPack)(sourceDir);
|
|
24767
24963
|
const outPath = options.out ?? result.suggestedFileName;
|
|
24768
|
-
|
|
24964
|
+
fs23.writeFileSync(outPath, result.archive);
|
|
24769
24965
|
logger.success(`Built pack "${result.info.name}" v${result.info.version} \u2192 ${outPath} (${result.archive.byteLength} bytes)`);
|
|
24770
24966
|
logger.info(`Install it with \`wairon pack add ${outPath}\`, or upload it to a hosted instance.`);
|
|
24771
24967
|
}
|
|
@@ -24779,9 +24975,9 @@ async function listPacks() {
|
|
|
24779
24975
|
console.log(import_chalk16.default.bold.cyan(`\u25A0 Global (${globalPacksDir()})${useGlobal ? "" : import_chalk16.default.yellow(" [disabled: extensions.useGlobalPacks: false]")}`));
|
|
24780
24976
|
if (globalRefs.length === 0) console.log(import_chalk16.default.dim(" (none)"));
|
|
24781
24977
|
for (const ref of globalRefs) {
|
|
24782
|
-
const probe2 = probePack(ref,
|
|
24783
|
-
if (probe2.error) console.log(` ${import_chalk16.default.red("\u2716")} ${
|
|
24784
|
-
else console.log(` ${import_chalk16.default.green("\u25CF")} ${import_chalk16.default.bold(probe2.name ??
|
|
24978
|
+
const probe2 = probePack(ref, path32.dirname(ref), "global");
|
|
24979
|
+
if (probe2.error) console.log(` ${import_chalk16.default.red("\u2716")} ${path32.basename(ref)} \u2014 ${import_chalk16.default.red(probe2.error)}`);
|
|
24980
|
+
else console.log(` ${import_chalk16.default.green("\u25CF")} ${import_chalk16.default.bold(probe2.name ?? path32.basename(ref))} ${import_chalk16.default.dim(describe(probe2))}`);
|
|
24785
24981
|
}
|
|
24786
24982
|
console.log("");
|
|
24787
24983
|
if (!inProject) {
|
|
@@ -24802,9 +24998,9 @@ async function listPacks() {
|
|
|
24802
24998
|
async function removePack(name, options = {}) {
|
|
24803
24999
|
if (options.global) {
|
|
24804
25000
|
for (const ref of discoverPacks(globalPacksDir())) {
|
|
24805
|
-
const probe2 = probePack(ref,
|
|
24806
|
-
if (probe2.name === name ||
|
|
24807
|
-
|
|
25001
|
+
const probe2 = probePack(ref, path32.dirname(ref), "global");
|
|
25002
|
+
if (probe2.name === name || path32.basename(ref) === name) {
|
|
25003
|
+
fs23.rmSync(ref, { recursive: true, force: true });
|
|
24808
25004
|
logger.success(`Removed global pack "${probe2.name ?? name}" (${ref}).`);
|
|
24809
25005
|
return;
|
|
24810
25006
|
}
|
|
@@ -24823,16 +25019,16 @@ async function removePack(name, options = {}) {
|
|
|
24823
25019
|
const packs = config.extensions?.packs ?? [];
|
|
24824
25020
|
for (const ref of packs) {
|
|
24825
25021
|
const probe2 = probePack(ref, root, "project");
|
|
24826
|
-
if (probe2.name === name || ref === name ||
|
|
25022
|
+
if (probe2.name === name || ref === name || path32.basename(ref) === name) {
|
|
24827
25023
|
config.extensions = {
|
|
24828
25024
|
packs: packs.filter((p) => p !== ref),
|
|
24829
25025
|
useGlobalPacks: config.extensions?.useGlobalPacks ?? true
|
|
24830
25026
|
};
|
|
24831
25027
|
saveProjectConfig(config);
|
|
24832
|
-
const resolved =
|
|
24833
|
-
const vendorDir =
|
|
24834
|
-
if (resolved.startsWith(vendorDir +
|
|
24835
|
-
|
|
25028
|
+
const resolved = path32.resolve(root, ref);
|
|
25029
|
+
const vendorDir = path32.resolve(root, ".wai", "packs");
|
|
25030
|
+
if (resolved.startsWith(vendorDir + path32.sep)) {
|
|
25031
|
+
fs23.rmSync(resolved, { recursive: true, force: true });
|
|
24836
25032
|
logger.success(`Deregistered pack "${probe2.name ?? name}" and deleted ${ref}.`);
|
|
24837
25033
|
} else {
|
|
24838
25034
|
logger.success(`Deregistered pack "${probe2.name ?? name}" (files at ${ref} left in place).`);
|
|
@@ -24846,8 +25042,8 @@ async function removePack(name, options = {}) {
|
|
|
24846
25042
|
|
|
24847
25043
|
// src/commands/host.ts
|
|
24848
25044
|
var fs50 = __toESM(require("fs"));
|
|
24849
|
-
var
|
|
24850
|
-
var
|
|
25045
|
+
var path59 = __toESM(require("path"));
|
|
25046
|
+
var os10 = __toESM(require("os"));
|
|
24851
25047
|
var crypto20 = __toESM(require("crypto"));
|
|
24852
25048
|
var import_child_process5 = require("child_process");
|
|
24853
25049
|
var import_chalk17 = __toESM(require("chalk"));
|
|
@@ -24874,29 +25070,29 @@ var UNAUTHENTICATED = {
|
|
|
24874
25070
|
var WEB_SESSION_PREFIX = "ws_";
|
|
24875
25071
|
|
|
24876
25072
|
// src/server/credentials.ts
|
|
24877
|
-
var
|
|
24878
|
-
var
|
|
25073
|
+
var fs24 = __toESM(require("fs"));
|
|
25074
|
+
var path33 = __toESM(require("path"));
|
|
24879
25075
|
var crypto3 = __toESM(require("crypto"));
|
|
24880
25076
|
var HASH_NS = "wairon:token:v1";
|
|
24881
25077
|
function hashToken(token) {
|
|
24882
25078
|
return crypto3.createHash("sha256").update(`${HASH_NS}:${token}`).digest("hex");
|
|
24883
25079
|
}
|
|
24884
25080
|
function storePath(dataDir) {
|
|
24885
|
-
return
|
|
25081
|
+
return path33.join(dataDir, "auth", "credentials.json");
|
|
24886
25082
|
}
|
|
24887
25083
|
function load3(dataDir) {
|
|
24888
25084
|
try {
|
|
24889
|
-
return JSON.parse(
|
|
25085
|
+
return JSON.parse(fs24.readFileSync(storePath(dataDir), "utf8"));
|
|
24890
25086
|
} catch {
|
|
24891
25087
|
return [];
|
|
24892
25088
|
}
|
|
24893
25089
|
}
|
|
24894
25090
|
function save(dataDir, records) {
|
|
24895
25091
|
const p = storePath(dataDir);
|
|
24896
|
-
|
|
25092
|
+
fs24.mkdirSync(path33.dirname(p), { recursive: true });
|
|
24897
25093
|
const tmp = `${p}.tmp`;
|
|
24898
|
-
|
|
24899
|
-
|
|
25094
|
+
fs24.writeFileSync(tmp, JSON.stringify(records, null, 2) + "\n");
|
|
25095
|
+
fs24.renameSync(tmp, p);
|
|
24900
25096
|
}
|
|
24901
25097
|
function digestEquals(a, b) {
|
|
24902
25098
|
const ab = Buffer.from(a, "hex");
|
|
@@ -24941,17 +25137,17 @@ function listByOwner(dataDir, ownerUserId) {
|
|
|
24941
25137
|
}
|
|
24942
25138
|
|
|
24943
25139
|
// src/server/websessions.ts
|
|
24944
|
-
var
|
|
24945
|
-
var
|
|
25140
|
+
var fs25 = __toESM(require("fs"));
|
|
25141
|
+
var path34 = __toESM(require("path"));
|
|
24946
25142
|
var crypto4 = __toESM(require("crypto"));
|
|
24947
25143
|
function storePath2(dataDir) {
|
|
24948
|
-
return
|
|
25144
|
+
return path34.join(dataDir, "web-sessions.json");
|
|
24949
25145
|
}
|
|
24950
25146
|
function readSessions(dataDir) {
|
|
24951
25147
|
const p = storePath2(dataDir);
|
|
24952
25148
|
let raw;
|
|
24953
25149
|
try {
|
|
24954
|
-
raw =
|
|
25150
|
+
raw = fs25.readFileSync(p, "utf8");
|
|
24955
25151
|
} catch (e) {
|
|
24956
25152
|
if (e.code === "ENOENT") return [];
|
|
24957
25153
|
throw new Error(`Failed to read web session store at ${p}: ${e.message}`);
|
|
@@ -24966,10 +25162,10 @@ function readSessions(dataDir) {
|
|
|
24966
25162
|
}
|
|
24967
25163
|
function persistSessions(dataDir, sessions) {
|
|
24968
25164
|
const p = storePath2(dataDir);
|
|
24969
|
-
|
|
25165
|
+
fs25.mkdirSync(path34.dirname(p), { recursive: true });
|
|
24970
25166
|
const tmp = `${p}.tmp`;
|
|
24971
|
-
|
|
24972
|
-
|
|
25167
|
+
fs25.writeFileSync(tmp, JSON.stringify(sessions, null, 2) + "\n");
|
|
25168
|
+
fs25.renameSync(tmp, p);
|
|
24973
25169
|
}
|
|
24974
25170
|
function mintSessionId() {
|
|
24975
25171
|
return `${WEB_SESSION_PREFIX}${crypto4.randomBytes(24).toString("hex")}`;
|
|
@@ -25130,17 +25326,17 @@ function listWebSessionsBySubject(dataDir, userId) {
|
|
|
25130
25326
|
}
|
|
25131
25327
|
|
|
25132
25328
|
// src/server/users.ts
|
|
25133
|
-
var
|
|
25134
|
-
var
|
|
25329
|
+
var fs26 = __toESM(require("fs"));
|
|
25330
|
+
var path35 = __toESM(require("path"));
|
|
25135
25331
|
var VALID_STATUSES = ["active", "inactive", "suspended", "deactivated", "disabled"];
|
|
25136
25332
|
function storePath3(dataDir) {
|
|
25137
|
-
return
|
|
25333
|
+
return path35.join(dataDir, "users.json");
|
|
25138
25334
|
}
|
|
25139
25335
|
function loadStore(dataDir) {
|
|
25140
25336
|
const p = storePath3(dataDir);
|
|
25141
25337
|
let raw;
|
|
25142
25338
|
try {
|
|
25143
|
-
raw =
|
|
25339
|
+
raw = fs26.readFileSync(p, "utf8");
|
|
25144
25340
|
} catch (err) {
|
|
25145
25341
|
if (err.code === "ENOENT") return [];
|
|
25146
25342
|
throw new Error(`Cannot read hosted-user store at ${p}: ${err.message}`);
|
|
@@ -25158,10 +25354,10 @@ function loadStore(dataDir) {
|
|
|
25158
25354
|
}
|
|
25159
25355
|
function replaceAll(dataDir, records) {
|
|
25160
25356
|
const p = storePath3(dataDir);
|
|
25161
|
-
|
|
25357
|
+
fs26.mkdirSync(path35.dirname(p), { recursive: true });
|
|
25162
25358
|
const tmp = `${p}.tmp`;
|
|
25163
|
-
|
|
25164
|
-
|
|
25359
|
+
fs26.writeFileSync(tmp, JSON.stringify(records, null, 2) + "\n");
|
|
25360
|
+
fs26.renameSync(tmp, p);
|
|
25165
25361
|
}
|
|
25166
25362
|
function registryUpsert(dataDir, record2) {
|
|
25167
25363
|
const records = loadStore(dataDir);
|
|
@@ -25263,11 +25459,11 @@ function remapUnitReferences(dataDir, remap, removedScopeIds) {
|
|
|
25263
25459
|
}
|
|
25264
25460
|
|
|
25265
25461
|
// src/server/instance.ts
|
|
25266
|
-
var
|
|
25267
|
-
var
|
|
25462
|
+
var fs27 = __toESM(require("fs"));
|
|
25463
|
+
var path36 = __toESM(require("path"));
|
|
25268
25464
|
var import_crypto = require("crypto");
|
|
25269
25465
|
function storePath4(dataDir) {
|
|
25270
|
-
return
|
|
25466
|
+
return path36.join(dataDir, "instance.json");
|
|
25271
25467
|
}
|
|
25272
25468
|
var InstanceIdentityStore = class {
|
|
25273
25469
|
constructor(dataDir) {
|
|
@@ -25284,7 +25480,7 @@ var InstanceIdentityStore = class {
|
|
|
25284
25480
|
const p = storePath4(this.dataDir);
|
|
25285
25481
|
let raw;
|
|
25286
25482
|
try {
|
|
25287
|
-
raw =
|
|
25483
|
+
raw = fs27.readFileSync(p, "utf8");
|
|
25288
25484
|
} catch (err) {
|
|
25289
25485
|
if (err.code === "ENOENT") return null;
|
|
25290
25486
|
throw new Error(`Cannot read instance identity at ${p}: ${err.message}`);
|
|
@@ -25307,10 +25503,10 @@ var InstanceIdentityStore = class {
|
|
|
25307
25503
|
* never truncates the file. Only called by the registry's create-once seed. */
|
|
25308
25504
|
replace(identity) {
|
|
25309
25505
|
const p = storePath4(this.dataDir);
|
|
25310
|
-
|
|
25506
|
+
fs27.mkdirSync(path36.dirname(p), { recursive: true });
|
|
25311
25507
|
const tmp = `${p}.tmp`;
|
|
25312
|
-
|
|
25313
|
-
|
|
25508
|
+
fs27.writeFileSync(tmp, JSON.stringify(identity, null, 2) + "\n");
|
|
25509
|
+
fs27.renameSync(tmp, p);
|
|
25314
25510
|
}
|
|
25315
25511
|
};
|
|
25316
25512
|
var InstanceIdentityRegistry = class {
|
|
@@ -25366,8 +25562,8 @@ function getInstanceIdentity(dataDir) {
|
|
|
25366
25562
|
}
|
|
25367
25563
|
|
|
25368
25564
|
// src/utils/secrets.ts
|
|
25369
|
-
var
|
|
25370
|
-
var
|
|
25565
|
+
var fs28 = __toESM(require("fs"));
|
|
25566
|
+
var path37 = __toESM(require("path"));
|
|
25371
25567
|
var ENV_FALLBACK = {
|
|
25372
25568
|
"git-token": ["WAIRON_GIT_TOKEN"],
|
|
25373
25569
|
"notion-token": ["WAIRON_NOTION_TOKEN"],
|
|
@@ -25376,13 +25572,13 @@ var ENV_FALLBACK = {
|
|
|
25376
25572
|
};
|
|
25377
25573
|
function storePath5() {
|
|
25378
25574
|
const dataDir = process.env["WAIRON_DATA_DIR"];
|
|
25379
|
-
return dataDir ?
|
|
25575
|
+
return dataDir ? path37.join(dataDir, "auth", "secrets.json") : null;
|
|
25380
25576
|
}
|
|
25381
25577
|
function readStore() {
|
|
25382
25578
|
const p = storePath5();
|
|
25383
25579
|
if (!p) return {};
|
|
25384
25580
|
try {
|
|
25385
|
-
return JSON.parse(
|
|
25581
|
+
return JSON.parse(fs28.readFileSync(p, "utf8"));
|
|
25386
25582
|
} catch {
|
|
25387
25583
|
return {};
|
|
25388
25584
|
}
|
|
@@ -25407,10 +25603,10 @@ function setSecret(key, value) {
|
|
|
25407
25603
|
if (!p) throw new Error("WAIRON_DATA_DIR is not set \u2014 a running server needs it to store secrets.");
|
|
25408
25604
|
const store = readStore();
|
|
25409
25605
|
store[key] = value;
|
|
25410
|
-
|
|
25606
|
+
fs28.mkdirSync(path37.dirname(p), { recursive: true });
|
|
25411
25607
|
const tmp = `${p}.tmp`;
|
|
25412
|
-
|
|
25413
|
-
|
|
25608
|
+
fs28.writeFileSync(tmp, JSON.stringify(store, null, 2) + "\n");
|
|
25609
|
+
fs28.renameSync(tmp, p);
|
|
25414
25610
|
}
|
|
25415
25611
|
function listSecretKeys() {
|
|
25416
25612
|
return Object.keys(readStore());
|
|
@@ -25618,17 +25814,17 @@ function verifySsoState(state) {
|
|
|
25618
25814
|
}
|
|
25619
25815
|
|
|
25620
25816
|
// src/server/organization.ts
|
|
25621
|
-
var
|
|
25622
|
-
var
|
|
25817
|
+
var fs29 = __toESM(require("fs"));
|
|
25818
|
+
var path38 = __toESM(require("path"));
|
|
25623
25819
|
var crypto6 = __toESM(require("crypto"));
|
|
25624
25820
|
function storePath6(dataDir) {
|
|
25625
|
-
return
|
|
25821
|
+
return path38.join(dataDir, "organization.json");
|
|
25626
25822
|
}
|
|
25627
25823
|
function readState(dataDir) {
|
|
25628
25824
|
const p = storePath6(dataDir);
|
|
25629
25825
|
let raw;
|
|
25630
25826
|
try {
|
|
25631
|
-
raw =
|
|
25827
|
+
raw = fs29.readFileSync(p, "utf8");
|
|
25632
25828
|
} catch (e) {
|
|
25633
25829
|
if (e.code === "ENOENT") return { units: [], placements: [] };
|
|
25634
25830
|
throw new Error(`Failed to read organization store at ${p}: ${e.message}`);
|
|
@@ -25645,10 +25841,10 @@ function readState(dataDir) {
|
|
|
25645
25841
|
}
|
|
25646
25842
|
function persistState(dataDir, state) {
|
|
25647
25843
|
const p = storePath6(dataDir);
|
|
25648
|
-
|
|
25844
|
+
fs29.mkdirSync(path38.dirname(p), { recursive: true });
|
|
25649
25845
|
const tmp = `${p}.tmp`;
|
|
25650
|
-
|
|
25651
|
-
|
|
25846
|
+
fs29.writeFileSync(tmp, JSON.stringify(state, null, 2) + "\n");
|
|
25847
|
+
fs29.renameSync(tmp, p);
|
|
25652
25848
|
}
|
|
25653
25849
|
var SLUG_PATTERN = /^[a-z0-9-]+$/;
|
|
25654
25850
|
var UNIT_KINDS = ["business_entity", "department", "team", "group"];
|
|
@@ -26017,11 +26213,11 @@ function getOrganizationUnit(dataDir, id) {
|
|
|
26017
26213
|
}
|
|
26018
26214
|
|
|
26019
26215
|
// src/server/permissions.ts
|
|
26020
|
-
var
|
|
26021
|
-
var
|
|
26216
|
+
var fs30 = __toESM(require("fs"));
|
|
26217
|
+
var path39 = __toESM(require("path"));
|
|
26022
26218
|
var import_crypto2 = require("crypto");
|
|
26023
26219
|
function storePath7(dataDir) {
|
|
26024
|
-
return
|
|
26220
|
+
return path39.join(dataDir, "permissions.json");
|
|
26025
26221
|
}
|
|
26026
26222
|
function assignmentKey(a) {
|
|
26027
26223
|
return [a.subjectKind, a.subjectId ?? "", a.scopeKind, a.scopeId ?? "", a.capability].join("|");
|
|
@@ -26030,7 +26226,7 @@ function load4(dataDir) {
|
|
|
26030
26226
|
const p = storePath7(dataDir);
|
|
26031
26227
|
let raw;
|
|
26032
26228
|
try {
|
|
26033
|
-
raw =
|
|
26229
|
+
raw = fs30.readFileSync(p, "utf8");
|
|
26034
26230
|
} catch (err) {
|
|
26035
26231
|
if (err.code === "ENOENT") return [];
|
|
26036
26232
|
throw new Error(`Cannot read permission store at ${p}: ${err.message}`);
|
|
@@ -26048,10 +26244,10 @@ function load4(dataDir) {
|
|
|
26048
26244
|
}
|
|
26049
26245
|
function replaceAll2(dataDir, assignments) {
|
|
26050
26246
|
const p = storePath7(dataDir);
|
|
26051
|
-
|
|
26247
|
+
fs30.mkdirSync(path39.dirname(p), { recursive: true });
|
|
26052
26248
|
const tmp = `${p}.tmp`;
|
|
26053
|
-
|
|
26054
|
-
|
|
26249
|
+
fs30.writeFileSync(tmp, JSON.stringify(assignments, null, 2) + "\n");
|
|
26250
|
+
fs30.renameSync(tmp, p);
|
|
26055
26251
|
}
|
|
26056
26252
|
function registrySet(dataDir, assignment) {
|
|
26057
26253
|
const assignments = load4(dataDir);
|
|
@@ -26137,8 +26333,8 @@ function getAssignment(dataDir, assignmentId) {
|
|
|
26137
26333
|
}
|
|
26138
26334
|
|
|
26139
26335
|
// src/server/roles.ts
|
|
26140
|
-
var
|
|
26141
|
-
var
|
|
26336
|
+
var fs31 = __toESM(require("fs"));
|
|
26337
|
+
var path40 = __toESM(require("path"));
|
|
26142
26338
|
var BUILTIN_ROLES = [
|
|
26143
26339
|
{
|
|
26144
26340
|
id: SSO_ADMIN_ROLE_ID,
|
|
@@ -26156,13 +26352,13 @@ function isBuiltinRoleId(roleId) {
|
|
|
26156
26352
|
return BUILTIN_ROLE_IDS.has(roleId);
|
|
26157
26353
|
}
|
|
26158
26354
|
function storePath8(dataDir) {
|
|
26159
|
-
return
|
|
26355
|
+
return path40.join(dataDir, "roles.json");
|
|
26160
26356
|
}
|
|
26161
26357
|
function load5(dataDir) {
|
|
26162
26358
|
const p = storePath8(dataDir);
|
|
26163
26359
|
let raw;
|
|
26164
26360
|
try {
|
|
26165
|
-
raw =
|
|
26361
|
+
raw = fs31.readFileSync(p, "utf8");
|
|
26166
26362
|
} catch (err) {
|
|
26167
26363
|
if (err.code === "ENOENT") return [];
|
|
26168
26364
|
throw new Error(`Cannot read role store at ${p}: ${err.message}`);
|
|
@@ -26180,10 +26376,10 @@ function load5(dataDir) {
|
|
|
26180
26376
|
}
|
|
26181
26377
|
function replaceAll3(dataDir, roles) {
|
|
26182
26378
|
const p = storePath8(dataDir);
|
|
26183
|
-
|
|
26379
|
+
fs31.mkdirSync(path40.dirname(p), { recursive: true });
|
|
26184
26380
|
const tmp = `${p}.tmp`;
|
|
26185
|
-
|
|
26186
|
-
|
|
26381
|
+
fs31.writeFileSync(tmp, JSON.stringify(roles, null, 2) + "\n");
|
|
26382
|
+
fs31.renameSync(tmp, p);
|
|
26187
26383
|
}
|
|
26188
26384
|
function registryCreate(dataDir, role) {
|
|
26189
26385
|
if (isBuiltinRoleId(role.id)) {
|
|
@@ -26417,131 +26613,14 @@ function actionableUnitIds(scopes) {
|
|
|
26417
26613
|
}
|
|
26418
26614
|
|
|
26419
26615
|
// src/server/projects.ts
|
|
26420
|
-
var
|
|
26421
|
-
var
|
|
26422
|
-
|
|
26423
|
-
|
|
26424
|
-
return typeof id === "string" && ID_RE.test(id);
|
|
26425
|
-
}
|
|
26426
|
-
function registryPath(dataDir) {
|
|
26427
|
-
return path40.join(dataDir, "projects.json");
|
|
26428
|
-
}
|
|
26429
|
-
function load6(dataDir) {
|
|
26430
|
-
try {
|
|
26431
|
-
return JSON.parse(fs31.readFileSync(registryPath(dataDir), "utf8"));
|
|
26432
|
-
} catch {
|
|
26433
|
-
return [];
|
|
26434
|
-
}
|
|
26435
|
-
}
|
|
26436
|
-
function save2(dataDir, records) {
|
|
26437
|
-
const p = registryPath(dataDir);
|
|
26438
|
-
fs31.mkdirSync(path40.dirname(p), { recursive: true });
|
|
26439
|
-
const tmp = `${p}.tmp`;
|
|
26440
|
-
fs31.writeFileSync(tmp, JSON.stringify(records, null, 2) + "\n");
|
|
26441
|
-
fs31.renameSync(tmp, p);
|
|
26442
|
-
}
|
|
26443
|
-
function projectRoot(dataDir, id) {
|
|
26444
|
-
return path40.join(dataDir, "projects", id);
|
|
26445
|
-
}
|
|
26446
|
-
function existingProjectRoot(dataDir, id) {
|
|
26447
|
-
if (!isValidProjectId(id)) return null;
|
|
26448
|
-
const rec = load6(dataDir).find((r) => r.id === id);
|
|
26449
|
-
return rec ? rec.rootPath : null;
|
|
26450
|
-
}
|
|
26451
|
-
function createProjectRecord(dataDir, id) {
|
|
26452
|
-
if (!isValidProjectId(id)) {
|
|
26453
|
-
throw new Error(`Invalid project id "${id}" (allowed: lowercase letters, digits, hyphen).`);
|
|
26454
|
-
}
|
|
26455
|
-
const records = load6(dataDir);
|
|
26456
|
-
if (records.some((r) => r.id === id)) {
|
|
26457
|
-
throw new Error(`Project "${id}" already exists.`);
|
|
26458
|
-
}
|
|
26459
|
-
const root = projectRoot(dataDir, id);
|
|
26460
|
-
fs31.mkdirSync(root, { recursive: true });
|
|
26461
|
-
const record2 = {
|
|
26462
|
-
id,
|
|
26463
|
-
rootPath: root,
|
|
26464
|
-
status: "active",
|
|
26465
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
26466
|
-
};
|
|
26467
|
-
records.push(record2);
|
|
26468
|
-
save2(dataDir, records);
|
|
26469
|
-
return record2;
|
|
26470
|
-
}
|
|
26471
|
-
function registerLocalDevProject(dataDir, id, rootPath) {
|
|
26472
|
-
if (!isValidProjectId(id)) {
|
|
26473
|
-
throw new Error(`Invalid project id "${id}" (allowed: lowercase letters, digits, hyphen).`);
|
|
26474
|
-
}
|
|
26475
|
-
const records = load6(dataDir);
|
|
26476
|
-
const existing = records.find((r) => r.id === id);
|
|
26477
|
-
const record2 = {
|
|
26478
|
-
id,
|
|
26479
|
-
rootPath,
|
|
26480
|
-
status: "active",
|
|
26481
|
-
createdAt: existing?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
26482
|
-
};
|
|
26483
|
-
const next = existing ? records.map((r) => r.id === id ? record2 : r) : [...records, record2];
|
|
26484
|
-
save2(dataDir, next);
|
|
26485
|
-
return record2;
|
|
26486
|
-
}
|
|
26487
|
-
function listProjectRecords(dataDir) {
|
|
26488
|
-
return load6(dataDir);
|
|
26489
|
-
}
|
|
26490
|
-
function removeProjectRecord(dataDir, id) {
|
|
26491
|
-
const records = load6(dataDir);
|
|
26492
|
-
const rec = records.find((r) => r.id === id);
|
|
26493
|
-
if (rec) {
|
|
26494
|
-
try {
|
|
26495
|
-
fs31.rmSync(rec.rootPath, { recursive: true, force: true });
|
|
26496
|
-
} catch {
|
|
26497
|
-
}
|
|
26498
|
-
}
|
|
26499
|
-
save2(dataDir, records.filter((r) => r.id !== id));
|
|
26500
|
-
}
|
|
26501
|
-
function resolveProjectRoot(dataDir, principal, selector) {
|
|
26502
|
-
const authorized = principal.projects;
|
|
26503
|
-
const wildcard = authorized.includes("*");
|
|
26504
|
-
let target;
|
|
26505
|
-
if (selector) {
|
|
26506
|
-
if (!wildcard && !authorized.includes(selector)) return null;
|
|
26507
|
-
target = selector;
|
|
26508
|
-
} else if (!wildcard && authorized.length === 1) {
|
|
26509
|
-
target = authorized[0];
|
|
26510
|
-
} else {
|
|
26511
|
-
return null;
|
|
26512
|
-
}
|
|
26513
|
-
if (!isValidProjectId(target)) return null;
|
|
26514
|
-
const rec = load6(dataDir).find((r) => r.id === target);
|
|
26515
|
-
if (!rec || rec.status !== "active") return null;
|
|
26516
|
-
return rec.rootPath;
|
|
26517
|
-
}
|
|
26518
|
-
|
|
26519
|
-
// src/server/adapters.ts
|
|
26520
|
-
init_statehash();
|
|
26521
|
-
|
|
26522
|
-
// src/core/lockfile.ts
|
|
26523
|
-
var fs32 = __toESM(require("fs"));
|
|
26524
|
-
var path41 = __toESM(require("path"));
|
|
26616
|
+
var fs35 = __toESM(require("fs"));
|
|
26617
|
+
var path44 = __toESM(require("path"));
|
|
26618
|
+
init_loader();
|
|
26619
|
+
init_yaml();
|
|
26525
26620
|
init_fs();
|
|
26526
|
-
function lockPath() {
|
|
26527
|
-
return aiDir("lock.json");
|
|
26528
|
-
}
|
|
26529
|
-
function readLockRecord() {
|
|
26530
|
-
try {
|
|
26531
|
-
return JSON.parse(fs32.readFileSync(lockPath(), "utf8"));
|
|
26532
|
-
} catch {
|
|
26533
|
-
return null;
|
|
26534
|
-
}
|
|
26535
|
-
}
|
|
26536
|
-
function writeLockRecord(record2) {
|
|
26537
|
-
const p = lockPath();
|
|
26538
|
-
fs32.mkdirSync(path41.dirname(p), { recursive: true });
|
|
26539
|
-
const tmp = `${p}.tmp`;
|
|
26540
|
-
fs32.writeFileSync(tmp, JSON.stringify(record2, null, 2) + "\n");
|
|
26541
|
-
fs32.renameSync(tmp, p);
|
|
26542
|
-
}
|
|
26543
26621
|
|
|
26544
26622
|
// src/server/adapters.ts
|
|
26623
|
+
init_statehash();
|
|
26545
26624
|
init_specs2();
|
|
26546
26625
|
init_provision();
|
|
26547
26626
|
init_validation();
|
|
@@ -26552,37 +26631,37 @@ init_types();
|
|
|
26552
26631
|
init_server();
|
|
26553
26632
|
|
|
26554
26633
|
// src/git/config.ts
|
|
26555
|
-
var
|
|
26556
|
-
var
|
|
26634
|
+
var fs32 = __toESM(require("fs"));
|
|
26635
|
+
var path41 = __toESM(require("path"));
|
|
26557
26636
|
init_fs();
|
|
26558
26637
|
function configPath() {
|
|
26559
26638
|
return aiDir("git.json");
|
|
26560
26639
|
}
|
|
26561
26640
|
function readGitConfig() {
|
|
26562
26641
|
try {
|
|
26563
|
-
return JSON.parse(
|
|
26642
|
+
return JSON.parse(fs32.readFileSync(configPath(), "utf8"));
|
|
26564
26643
|
} catch {
|
|
26565
26644
|
return null;
|
|
26566
26645
|
}
|
|
26567
26646
|
}
|
|
26568
26647
|
function writeGitConfig(config) {
|
|
26569
26648
|
const p = configPath();
|
|
26570
|
-
|
|
26649
|
+
fs32.mkdirSync(path41.dirname(p), { recursive: true });
|
|
26571
26650
|
const tmp = `${p}.tmp`;
|
|
26572
|
-
|
|
26573
|
-
|
|
26651
|
+
fs32.writeFileSync(tmp, JSON.stringify(config, null, 2) + "\n");
|
|
26652
|
+
fs32.renameSync(tmp, p);
|
|
26574
26653
|
}
|
|
26575
26654
|
function clearGitConfig() {
|
|
26576
26655
|
try {
|
|
26577
|
-
|
|
26656
|
+
fs32.rmSync(configPath(), { force: true });
|
|
26578
26657
|
} catch {
|
|
26579
26658
|
}
|
|
26580
26659
|
}
|
|
26581
26660
|
|
|
26582
26661
|
// src/git/adapter.ts
|
|
26583
26662
|
var import_child_process3 = require("child_process");
|
|
26584
|
-
var
|
|
26585
|
-
var
|
|
26663
|
+
var fs33 = __toESM(require("fs"));
|
|
26664
|
+
var path42 = __toESM(require("path"));
|
|
26586
26665
|
init_fs();
|
|
26587
26666
|
function git(args, cwd) {
|
|
26588
26667
|
return (0, import_child_process3.execFileSync)("git", args, {
|
|
@@ -26634,10 +26713,10 @@ function compareUrl(remote, defaultBranch, workingBranch) {
|
|
|
26634
26713
|
return `${web}/compare/${encodeURIComponent(defaultBranch)}...${encodeURIComponent(workingBranch)}`;
|
|
26635
26714
|
}
|
|
26636
26715
|
function excludeLocalFiles() {
|
|
26637
|
-
const excludePath =
|
|
26716
|
+
const excludePath = path42.join(getProjectRoot(), ".git", "info", "exclude");
|
|
26638
26717
|
try {
|
|
26639
|
-
|
|
26640
|
-
|
|
26718
|
+
fs33.mkdirSync(path42.dirname(excludePath), { recursive: true });
|
|
26719
|
+
fs33.appendFileSync(excludePath, "\n.wai/lock.json\n.wai/git.json\n");
|
|
26641
26720
|
} catch {
|
|
26642
26721
|
}
|
|
26643
26722
|
}
|
|
@@ -26702,39 +26781,39 @@ function configureSync(periodicSyncMinutes, skipIfClean) {
|
|
|
26702
26781
|
}
|
|
26703
26782
|
|
|
26704
26783
|
// src/producers/config.ts
|
|
26705
|
-
var
|
|
26706
|
-
var
|
|
26784
|
+
var fs34 = __toESM(require("fs"));
|
|
26785
|
+
var path43 = __toESM(require("path"));
|
|
26707
26786
|
init_fs();
|
|
26708
26787
|
function configPath2() {
|
|
26709
26788
|
return aiDir("producers.json");
|
|
26710
26789
|
}
|
|
26711
|
-
function
|
|
26790
|
+
function load6() {
|
|
26712
26791
|
try {
|
|
26713
|
-
return JSON.parse(
|
|
26792
|
+
return JSON.parse(fs34.readFileSync(configPath2(), "utf8"));
|
|
26714
26793
|
} catch {
|
|
26715
26794
|
return [];
|
|
26716
26795
|
}
|
|
26717
26796
|
}
|
|
26718
|
-
function
|
|
26797
|
+
function save2(configs) {
|
|
26719
26798
|
const p = configPath2();
|
|
26720
|
-
|
|
26799
|
+
fs34.mkdirSync(path43.dirname(p), { recursive: true });
|
|
26721
26800
|
const tmp = `${p}.tmp`;
|
|
26722
|
-
|
|
26723
|
-
|
|
26801
|
+
fs34.writeFileSync(tmp, JSON.stringify(configs, null, 2) + "\n");
|
|
26802
|
+
fs34.renameSync(tmp, p);
|
|
26724
26803
|
}
|
|
26725
26804
|
function readProducerConfig(target) {
|
|
26726
|
-
return
|
|
26805
|
+
return load6().find((c) => c.target === target) ?? null;
|
|
26727
26806
|
}
|
|
26728
26807
|
function writeProducerConfig(config) {
|
|
26729
|
-
const configs =
|
|
26808
|
+
const configs = load6().filter((c) => c.target !== config.target);
|
|
26730
26809
|
configs.push(config);
|
|
26731
|
-
|
|
26810
|
+
save2(configs);
|
|
26732
26811
|
}
|
|
26733
26812
|
function clearProducerConfig(target) {
|
|
26734
|
-
|
|
26813
|
+
save2(load6().filter((c) => c.target !== target));
|
|
26735
26814
|
}
|
|
26736
26815
|
function listProducerConfigs() {
|
|
26737
|
-
return
|
|
26816
|
+
return load6();
|
|
26738
26817
|
}
|
|
26739
26818
|
|
|
26740
26819
|
// src/producers/core-adapter.ts
|
|
@@ -27073,8 +27152,19 @@ var hostCore = {
|
|
|
27073
27152
|
/** The ids of wairon's built-in architectural profiles, read from the core rules
|
|
27074
27153
|
* registry's built-in profile set (BUILTIN_PROFILES) — a pure, side-effect-free
|
|
27075
27154
|
* read of a bundled constant. */
|
|
27076
|
-
builtinProfileIds: () => [...BUILTIN_PROFILES]
|
|
27155
|
+
builtinProfileIds: () => [...BUILTIN_PROFILES],
|
|
27156
|
+
/** The ids of wairon's built-in COMPOSITE PROJECT KINDS, read from the core
|
|
27157
|
+
* rules registry's bundled constant (PROJECT_KINDS) — a pure, side-effect-free
|
|
27158
|
+
* read. The counterpart of builtinProfileIds: legal projectType values that
|
|
27159
|
+
* are not architectural profiles and carry no profile doctrine of their own,
|
|
27160
|
+
* so the hosted profile-application path recognizes a project kind as
|
|
27161
|
+
* resolvable-as-is (no contributing pack to adopt) instead of refusing it as
|
|
27162
|
+
* an unknown profile. */
|
|
27163
|
+
builtinProjectKinds: () => [...PROJECT_KINDS]
|
|
27077
27164
|
};
|
|
27165
|
+
function resolveContainedProjectPath(projectRoot2, projectPath) {
|
|
27166
|
+
return assertContainedProjectPath(projectRoot2, projectPath);
|
|
27167
|
+
}
|
|
27078
27168
|
function validateProjectAsComplete() {
|
|
27079
27169
|
const config = loadProjectConfig();
|
|
27080
27170
|
return validateAsComplete({ rules: config.rules, projectType: config.projectType });
|
|
@@ -27106,6 +27196,180 @@ var hostSdk = {
|
|
|
27106
27196
|
extractArchive: (archive, destDir, limits) => sdkPortal.extractPack(archive, destDir, limits)
|
|
27107
27197
|
};
|
|
27108
27198
|
|
|
27199
|
+
// src/server/projects.ts
|
|
27200
|
+
var ID_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
27201
|
+
function isValidProjectId(id) {
|
|
27202
|
+
return typeof id === "string" && ID_RE.test(id);
|
|
27203
|
+
}
|
|
27204
|
+
function registryPath(dataDir) {
|
|
27205
|
+
return path44.join(dataDir, "projects.json");
|
|
27206
|
+
}
|
|
27207
|
+
function load7(dataDir) {
|
|
27208
|
+
try {
|
|
27209
|
+
return JSON.parse(fs35.readFileSync(registryPath(dataDir), "utf8"));
|
|
27210
|
+
} catch {
|
|
27211
|
+
return [];
|
|
27212
|
+
}
|
|
27213
|
+
}
|
|
27214
|
+
function save3(dataDir, records) {
|
|
27215
|
+
const p = registryPath(dataDir);
|
|
27216
|
+
fs35.mkdirSync(path44.dirname(p), { recursive: true });
|
|
27217
|
+
const tmp = `${p}.tmp`;
|
|
27218
|
+
fs35.writeFileSync(tmp, JSON.stringify(records, null, 2) + "\n");
|
|
27219
|
+
fs35.renameSync(tmp, p);
|
|
27220
|
+
}
|
|
27221
|
+
function projectRoot(dataDir, id) {
|
|
27222
|
+
return path44.join(dataDir, "projects", id);
|
|
27223
|
+
}
|
|
27224
|
+
function existingProjectRoot(dataDir, id) {
|
|
27225
|
+
if (!isValidProjectId(id)) return null;
|
|
27226
|
+
const rec = load7(dataDir).find((r) => r.id === id);
|
|
27227
|
+
return rec ? rec.rootPath : null;
|
|
27228
|
+
}
|
|
27229
|
+
function createProjectRecord(dataDir, id) {
|
|
27230
|
+
if (!isValidProjectId(id)) {
|
|
27231
|
+
throw new Error(`Invalid project id "${id}" (allowed: lowercase letters, digits, hyphen).`);
|
|
27232
|
+
}
|
|
27233
|
+
const records = load7(dataDir);
|
|
27234
|
+
if (records.some((r) => r.id === id)) {
|
|
27235
|
+
throw new Error(`Project "${id}" already exists.`);
|
|
27236
|
+
}
|
|
27237
|
+
const root = projectRoot(dataDir, id);
|
|
27238
|
+
fs35.mkdirSync(root, { recursive: true });
|
|
27239
|
+
const record2 = {
|
|
27240
|
+
id,
|
|
27241
|
+
rootPath: root,
|
|
27242
|
+
status: "active",
|
|
27243
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
27244
|
+
};
|
|
27245
|
+
records.push(record2);
|
|
27246
|
+
save3(dataDir, records);
|
|
27247
|
+
return record2;
|
|
27248
|
+
}
|
|
27249
|
+
function registerLocalDevProject(dataDir, id, rootPath) {
|
|
27250
|
+
if (!isValidProjectId(id)) {
|
|
27251
|
+
throw new Error(`Invalid project id "${id}" (allowed: lowercase letters, digits, hyphen).`);
|
|
27252
|
+
}
|
|
27253
|
+
const records = load7(dataDir);
|
|
27254
|
+
const existing = records.find((r) => r.id === id);
|
|
27255
|
+
const record2 = {
|
|
27256
|
+
id,
|
|
27257
|
+
rootPath,
|
|
27258
|
+
status: "active",
|
|
27259
|
+
createdAt: existing?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
27260
|
+
};
|
|
27261
|
+
const next = existing ? records.map((r) => r.id === id ? record2 : r) : [...records, record2];
|
|
27262
|
+
save3(dataDir, next);
|
|
27263
|
+
return record2;
|
|
27264
|
+
}
|
|
27265
|
+
function listProjectRecords(dataDir) {
|
|
27266
|
+
return load7(dataDir);
|
|
27267
|
+
}
|
|
27268
|
+
function removeProjectRecord(dataDir, id) {
|
|
27269
|
+
const records = load7(dataDir);
|
|
27270
|
+
const rec = records.find((r) => r.id === id);
|
|
27271
|
+
if (rec) {
|
|
27272
|
+
try {
|
|
27273
|
+
fs35.rmSync(rec.rootPath, { recursive: true, force: true });
|
|
27274
|
+
} catch {
|
|
27275
|
+
}
|
|
27276
|
+
}
|
|
27277
|
+
save3(dataDir, records.filter((r) => r.id !== id));
|
|
27278
|
+
}
|
|
27279
|
+
var SUBPROJECT_SEPARATOR = "::";
|
|
27280
|
+
function parseQualifiedSelector(value) {
|
|
27281
|
+
if (typeof value !== "string" || value.length === 0) return null;
|
|
27282
|
+
const [projectId, ...mounts] = value.split(SUBPROJECT_SEPARATOR);
|
|
27283
|
+
if (!isValidProjectId(projectId)) return null;
|
|
27284
|
+
if (mounts.some((m) => m.trim() === "")) return null;
|
|
27285
|
+
return { projectId, mounts };
|
|
27286
|
+
}
|
|
27287
|
+
function findSubsystemSpec(root, subsystemId) {
|
|
27288
|
+
const specsDir = aiPathsAt(root).specsDir();
|
|
27289
|
+
if (!fs35.existsSync(specsDir)) return null;
|
|
27290
|
+
for (const file of listFilesRecursive(specsDir, ".yaml")) {
|
|
27291
|
+
let raw;
|
|
27292
|
+
try {
|
|
27293
|
+
raw = readYamlFile(file);
|
|
27294
|
+
} catch {
|
|
27295
|
+
continue;
|
|
27296
|
+
}
|
|
27297
|
+
if (!raw || typeof raw !== "object" || !("parentSystem" in raw)) continue;
|
|
27298
|
+
if (raw.id !== subsystemId) continue;
|
|
27299
|
+
const pp = raw.projectPath;
|
|
27300
|
+
return typeof pp === "string" && pp.trim() !== "" ? { projectPath: pp } : {};
|
|
27301
|
+
}
|
|
27302
|
+
return null;
|
|
27303
|
+
}
|
|
27304
|
+
function resolveSubprojectMounts(projectId, projectRoot2, mounts) {
|
|
27305
|
+
let root = projectRoot2;
|
|
27306
|
+
let at = projectId;
|
|
27307
|
+
for (const mount of mounts) {
|
|
27308
|
+
const sub = findSubsystemSpec(root, mount);
|
|
27309
|
+
if (!sub) {
|
|
27310
|
+
throw new Error(
|
|
27311
|
+
`unknown subproject mount "${mount}" on "${at}" \u2014 no subsystem with that id exists in its spec tree`
|
|
27312
|
+
);
|
|
27313
|
+
}
|
|
27314
|
+
if (!sub.projectPath) {
|
|
27315
|
+
throw new Error(
|
|
27316
|
+
`subsystem "${mount}" on "${at}" is not a chained subproject (it carries no projectPath) \u2014 only a subsystem mounted via projectPath can be bound as a subproject`
|
|
27317
|
+
);
|
|
27318
|
+
}
|
|
27319
|
+
root = resolveContainedProjectPath(root, sub.projectPath);
|
|
27320
|
+
at = `${at}${SUBPROJECT_SEPARATOR}${mount}`;
|
|
27321
|
+
}
|
|
27322
|
+
return root;
|
|
27323
|
+
}
|
|
27324
|
+
function assertMintableNarrowingEntry(dataDir, entry) {
|
|
27325
|
+
if (entry === "*") return;
|
|
27326
|
+
const parsed = parseQualifiedSelector(entry);
|
|
27327
|
+
if (!parsed) {
|
|
27328
|
+
throw new Error(
|
|
27329
|
+
`invalid project narrowing entry "${entry}" (expected a project id, optionally subproject-qualified as projectId::subsystemId)`
|
|
27330
|
+
);
|
|
27331
|
+
}
|
|
27332
|
+
const rec = load7(dataDir).find((r) => r.id === parsed.projectId);
|
|
27333
|
+
if (!rec) throw new Error(`unknown project "${parsed.projectId}"`);
|
|
27334
|
+
if (parsed.mounts.length > 0) {
|
|
27335
|
+
resolveSubprojectMounts(parsed.projectId, rec.rootPath, parsed.mounts);
|
|
27336
|
+
}
|
|
27337
|
+
}
|
|
27338
|
+
function narrowingCovers(entry, target) {
|
|
27339
|
+
return target === entry || target.startsWith(entry + SUBPROJECT_SEPARATOR);
|
|
27340
|
+
}
|
|
27341
|
+
function resolveProjectBinding(dataDir, principal, selector) {
|
|
27342
|
+
const authorized = principal.projects;
|
|
27343
|
+
const wildcard = authorized.includes("*");
|
|
27344
|
+
let target;
|
|
27345
|
+
if (selector) {
|
|
27346
|
+
if (!wildcard && !authorized.some((e) => e !== "*" && narrowingCovers(e, selector))) return null;
|
|
27347
|
+
target = selector;
|
|
27348
|
+
} else if (!wildcard && authorized.length === 1) {
|
|
27349
|
+
target = authorized[0];
|
|
27350
|
+
} else {
|
|
27351
|
+
return null;
|
|
27352
|
+
}
|
|
27353
|
+
const parsed = parseQualifiedSelector(target);
|
|
27354
|
+
if (!parsed) return null;
|
|
27355
|
+
const rec = load7(dataDir).find((r) => r.id === parsed.projectId);
|
|
27356
|
+
if (!rec || rec.status !== "active") return null;
|
|
27357
|
+
let rootPath = rec.rootPath;
|
|
27358
|
+
if (parsed.mounts.length > 0) {
|
|
27359
|
+
try {
|
|
27360
|
+
rootPath = resolveSubprojectMounts(parsed.projectId, rec.rootPath, parsed.mounts);
|
|
27361
|
+
} catch {
|
|
27362
|
+
return null;
|
|
27363
|
+
}
|
|
27364
|
+
}
|
|
27365
|
+
const binding = { rootPath, projectId: parsed.projectId };
|
|
27366
|
+
if (parsed.mounts.length > 0) binding.subproject = parsed.mounts.join(SUBPROJECT_SEPARATOR);
|
|
27367
|
+
return binding;
|
|
27368
|
+
}
|
|
27369
|
+
function resolveProjectRoot(dataDir, principal, selector) {
|
|
27370
|
+
return resolveProjectBinding(dataDir, principal, selector)?.rootPath ?? null;
|
|
27371
|
+
}
|
|
27372
|
+
|
|
27109
27373
|
// src/server/errors.ts
|
|
27110
27374
|
var UnauthenticatedError = class extends Error {
|
|
27111
27375
|
constructor() {
|
|
@@ -27517,6 +27781,35 @@ function storeListGlobalPacks() {
|
|
|
27517
27781
|
);
|
|
27518
27782
|
return [...instance, ...image];
|
|
27519
27783
|
}
|
|
27784
|
+
function scanGlobalPackProfiles() {
|
|
27785
|
+
const out = [];
|
|
27786
|
+
for (const dir of [hostCore.globalPacksDir(), imagePacksDir()]) {
|
|
27787
|
+
for (const full of hostCore.discoverPacks(dir)) {
|
|
27788
|
+
try {
|
|
27789
|
+
const loaded = hostCore.loadExtensionPacks([{ ref: full, scope: "global" }], path45.dirname(full));
|
|
27790
|
+
if (loaded.errors.length) continue;
|
|
27791
|
+
const source = loaded.packNames[0] ?? path45.basename(full);
|
|
27792
|
+
for (const [id, def] of Object.entries(loaded.profiles)) out.push({ id, source, family: def.family });
|
|
27793
|
+
} catch {
|
|
27794
|
+
}
|
|
27795
|
+
}
|
|
27796
|
+
}
|
|
27797
|
+
return out;
|
|
27798
|
+
}
|
|
27799
|
+
function scanProjectPackProfiles() {
|
|
27800
|
+
const root = getProjectRoot();
|
|
27801
|
+
const out = [];
|
|
27802
|
+
for (const ref of loadProjectConfig().extensions?.packs ?? []) {
|
|
27803
|
+
try {
|
|
27804
|
+
const loaded = hostCore.loadExtensionPacks([{ ref, scope: "project" }], root);
|
|
27805
|
+
if (loaded.errors.length) continue;
|
|
27806
|
+
const source = loaded.packNames[0] ?? stem(ref);
|
|
27807
|
+
for (const [id, def] of Object.entries(loaded.profiles)) out.push({ id, source, family: def.family });
|
|
27808
|
+
} catch {
|
|
27809
|
+
}
|
|
27810
|
+
}
|
|
27811
|
+
return out;
|
|
27812
|
+
}
|
|
27520
27813
|
function storeListAvailableProfiles() {
|
|
27521
27814
|
const out = [];
|
|
27522
27815
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -27527,19 +27820,21 @@ function storeListAvailableProfiles() {
|
|
|
27527
27820
|
out.push(family ? { id, source, family } : { id, source });
|
|
27528
27821
|
};
|
|
27529
27822
|
for (const id of hostCore.builtinProfileIds()) emit(id, "builtin");
|
|
27530
|
-
const
|
|
27531
|
-
|
|
27532
|
-
|
|
27533
|
-
|
|
27534
|
-
|
|
27535
|
-
|
|
27536
|
-
|
|
27537
|
-
|
|
27538
|
-
|
|
27539
|
-
|
|
27823
|
+
for (const c of scanGlobalPackProfiles()) emit(c.id, c.source, c.family);
|
|
27824
|
+
return out;
|
|
27825
|
+
}
|
|
27826
|
+
function storeListProjectProfiles() {
|
|
27827
|
+
const out = [];
|
|
27828
|
+
const seen = /* @__PURE__ */ new Set();
|
|
27829
|
+
const emit = (id, source, installed, family) => {
|
|
27830
|
+
const key = JSON.stringify([id, source]);
|
|
27831
|
+
if (seen.has(key)) return;
|
|
27832
|
+
seen.add(key);
|
|
27833
|
+
out.push({ id, source, ...family ? { family } : {}, installed });
|
|
27540
27834
|
};
|
|
27541
|
-
|
|
27542
|
-
|
|
27835
|
+
for (const id of hostCore.builtinProfileIds()) emit(id, "builtin", true);
|
|
27836
|
+
for (const c of scanProjectPackProfiles()) emit(c.id, c.source, true, c.family);
|
|
27837
|
+
for (const c of scanGlobalPackProfiles()) emit(c.id, c.source, false, c.family);
|
|
27543
27838
|
return out;
|
|
27544
27839
|
}
|
|
27545
27840
|
function readPackContent(full) {
|
|
@@ -27732,6 +28027,10 @@ function listAvailableProfiles(cfg, credential) {
|
|
|
27732
28027
|
requirePrincipal2(cfg, credential);
|
|
27733
28028
|
return storeListAvailableProfiles();
|
|
27734
28029
|
}
|
|
28030
|
+
function listProjectProfiles(cfg, credential, project2) {
|
|
28031
|
+
requireCap(cfg, credential, "project:read", "project", project2, "Forbidden \u2014 listing a project's selectable profiles requires project:read over the project");
|
|
28032
|
+
return executeApprovedListProjectProfiles(cfg, project2);
|
|
28033
|
+
}
|
|
27735
28034
|
function listAdoptableProjectPacks(cfg, credential, project2) {
|
|
27736
28035
|
requireCap(cfg, credential, "project:read", "project", project2, "Forbidden \u2014 listing adoptable packs requires project:read over the project");
|
|
27737
28036
|
return storeListGlobalPacks();
|
|
@@ -27754,6 +28053,26 @@ function executeApprovedInstallProjectPack(cfg, project2, name, content) {
|
|
|
27754
28053
|
function executeApprovedResolveGlobalPacks(names) {
|
|
27755
28054
|
return storeResolveGlobalPacks(names);
|
|
27756
28055
|
}
|
|
28056
|
+
function executeApprovedListProjectProfiles(cfg, project2) {
|
|
28057
|
+
return runWithProjectRoot(boundProject2(cfg, project2), () => storeListProjectProfiles());
|
|
28058
|
+
}
|
|
28059
|
+
function executeApprovedEnsureProfileInstalled(cfg, project2, profileId) {
|
|
28060
|
+
if (hostCore.builtinProjectKinds().includes(profileId) || hostCore.builtinProfileIds().includes(profileId)) {
|
|
28061
|
+
return { profileId, source: "builtin" };
|
|
28062
|
+
}
|
|
28063
|
+
const contributors = executeApprovedListProjectProfiles(cfg, project2).filter((p) => p.id === profileId);
|
|
28064
|
+
const installed = contributors.find((p) => p.installed);
|
|
28065
|
+
if (installed) return { profileId, source: installed.source };
|
|
28066
|
+
const adoptable = contributors[0];
|
|
28067
|
+
const resolved = adoptable ? executeApprovedResolveGlobalPacks([adoptable.source]).resolved[0] : void 0;
|
|
28068
|
+
if (!resolved) {
|
|
28069
|
+
throw new Error(
|
|
28070
|
+
`Unknown profile "${profileId}" \u2014 no built-in profile or project kind carries it, no pack registered in project "${project2}" contributes it, and no server-global pack (mutable instance tier or immutable image tier) contributes it. Writing an unresolvable id as the projectType would silently disable the whole profile doctrine (UNKNOWN_PROFILE), so it is refused instead of applied.`
|
|
28071
|
+
);
|
|
28072
|
+
}
|
|
28073
|
+
executeApprovedInstallProjectPack(cfg, project2, resolved.name, resolved.content);
|
|
28074
|
+
return { profileId, source: resolved.name, adoptedPackName: resolved.name };
|
|
28075
|
+
}
|
|
27757
28076
|
function removeProjectPack(cfg, credential, project2, name) {
|
|
27758
28077
|
requireCap(cfg, credential, "project:admin", "project", project2, "Forbidden \u2014 removing a project pack requires project:admin over the project");
|
|
27759
28078
|
runWithProjectRoot(boundProject2(cfg, project2), () => storeRemoveProjectPack(name));
|
|
@@ -28748,6 +29067,40 @@ function requestPackNames(request) {
|
|
|
28748
29067
|
if (!sel) return [];
|
|
28749
29068
|
return [.../* @__PURE__ */ new Set([...sel.requiredPackNames ?? [], ...sel.defaultPackNames ?? []])];
|
|
28750
29069
|
}
|
|
29070
|
+
function classifyProfile(projectType, catalog) {
|
|
29071
|
+
if (hostCore.builtinProfileIds().includes(projectType) || hostCore.builtinProjectKinds().includes(projectType)) {
|
|
29072
|
+
return { source: "builtin", resolvable: true };
|
|
29073
|
+
}
|
|
29074
|
+
const installed = catalog.find((p) => p.id === projectType && p.installed);
|
|
29075
|
+
if (installed) return { source: installed.source, resolvable: true };
|
|
29076
|
+
return { resolvable: false };
|
|
29077
|
+
}
|
|
29078
|
+
function firstApplicableProfileId(candidates, catalog) {
|
|
29079
|
+
const kinds = hostCore.builtinProjectKinds();
|
|
29080
|
+
const catalogIds = new Set(catalog.map((p) => p.id));
|
|
29081
|
+
return candidates.find((id) => kinds.includes(id) || catalogIds.has(id));
|
|
29082
|
+
}
|
|
29083
|
+
function unappliedIds(selectedProfileIds, governingProfileId) {
|
|
29084
|
+
return selectedProfileIds.filter((id) => id !== governingProfileId);
|
|
29085
|
+
}
|
|
29086
|
+
function foldAppliedProfile(root, profileId, actor) {
|
|
29087
|
+
const recorded = readProjectProfileSelection(root);
|
|
29088
|
+
const folded = {
|
|
29089
|
+
profileIds: [profileId, ...unappliedIds(recorded?.profileIds ?? [], profileId)],
|
|
29090
|
+
requiredPackNames: recorded?.requiredPackNames ?? [],
|
|
29091
|
+
selectedBy: actor,
|
|
29092
|
+
selectedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
29093
|
+
};
|
|
29094
|
+
if (recorded?.defaultPackNames) folded.defaultPackNames = recorded.defaultPackNames;
|
|
29095
|
+
recordProjectProfileSelection(root, folded);
|
|
29096
|
+
return folded;
|
|
29097
|
+
}
|
|
29098
|
+
function overridingSubsystemIds(root) {
|
|
29099
|
+
return runWithProjectRoot(
|
|
29100
|
+
root,
|
|
29101
|
+
() => hostCore.loadSubsystemSpecs().filter((s) => !!s.profile).map((s) => s.id)
|
|
29102
|
+
);
|
|
29103
|
+
}
|
|
28751
29104
|
function resolvedSelection(request, policy, selectedBy) {
|
|
28752
29105
|
const sel = request.profileSelection;
|
|
28753
29106
|
const selection = {
|
|
@@ -28766,7 +29119,8 @@ function buildEvaluation(input) {
|
|
|
28766
29119
|
selectedProfileIds,
|
|
28767
29120
|
hasSelection,
|
|
28768
29121
|
countMissingPacksAsViolation,
|
|
28769
|
-
requiredDefaultResolution
|
|
29122
|
+
requiredDefaultResolution,
|
|
29123
|
+
governingProfileId
|
|
28770
29124
|
} = input;
|
|
28771
29125
|
const present = new Set(presentPackNames);
|
|
28772
29126
|
let missingPackNames;
|
|
@@ -28787,6 +29141,7 @@ function buildEvaluation(input) {
|
|
|
28787
29141
|
const allowed = policy.allowedProfileIds;
|
|
28788
29142
|
const disallowedProfileIds = allowed && allowed.length > 0 ? selectedProfileIds.filter((id) => !allowed.includes(id)) : [];
|
|
28789
29143
|
const selectionRequiredUnmet = policy.requireProfileSelection && !hasSelection;
|
|
29144
|
+
const unappliedProfileIds = governingProfileId ? unappliedIds(selectedProfileIds, governingProfileId) : [];
|
|
28790
29145
|
const messages = [];
|
|
28791
29146
|
if (selectionRequiredUnmet) {
|
|
28792
29147
|
messages.push("Profile selection is required by policy but none was provided.");
|
|
@@ -28798,16 +29153,24 @@ function buildEvaluation(input) {
|
|
|
28798
29153
|
for (const n of blockedPackNames) messages.push(`Pack "${n}" is blocked by policy.`);
|
|
28799
29154
|
for (const id of missingProfileIds) messages.push(`Required profile "${id}" is not selected.`);
|
|
28800
29155
|
for (const id of disallowedProfileIds) messages.push(`Profile "${id}" is not permitted by policy.`);
|
|
29156
|
+
for (const id of unappliedProfileIds) {
|
|
29157
|
+
messages.push(
|
|
29158
|
+
`Selected profile "${id}" is recorded but does not govern the project (projectType is "${governingProfileId}") \u2014 a project has exactly one governing profile.`
|
|
29159
|
+
);
|
|
29160
|
+
}
|
|
28801
29161
|
const violation = selectionRequiredUnmet || blockedPackNames.length > 0 || missingProfileIds.length > 0 || disallowedProfileIds.length > 0 || countMissingPacksAsViolation && (missingPackNames.length > 0 || unresolvedPacks.length > 0);
|
|
28802
|
-
|
|
29162
|
+
const result = {
|
|
28803
29163
|
compliant: !violation,
|
|
28804
29164
|
mode: policy.enforcementMode,
|
|
28805
29165
|
missingPackNames,
|
|
28806
29166
|
blockedPackNames,
|
|
28807
29167
|
missingProfileIds,
|
|
28808
29168
|
unresolvedPacks,
|
|
29169
|
+
unappliedProfileIds,
|
|
28809
29170
|
messages
|
|
28810
29171
|
};
|
|
29172
|
+
if (governingProfileId) result.governingProfileId = governingProfileId;
|
|
29173
|
+
return result;
|
|
28811
29174
|
}
|
|
28812
29175
|
function performInit(cfg, request, principal) {
|
|
28813
29176
|
const policy = effectivePolicy(cfg.dataDir);
|
|
@@ -28827,17 +29190,24 @@ function performInit(cfg, request, principal) {
|
|
|
28827
29190
|
request.ownerUnitId,
|
|
28828
29191
|
principal ? principalSubject3(principal) : void 0
|
|
28829
29192
|
);
|
|
28830
|
-
|
|
28831
|
-
|
|
28832
|
-
|
|
28833
|
-
|
|
28834
|
-
|
|
28835
|
-
|
|
28836
|
-
...requestPackNames(request)
|
|
28837
|
-
])
|
|
28838
|
-
);
|
|
29193
|
+
const packResolution = executeApprovedResolveGlobalPacks([
|
|
29194
|
+
...policy.requiredGlobalPacks,
|
|
29195
|
+
...policy.defaultProjectPacks,
|
|
29196
|
+
...requestPackNames(request)
|
|
29197
|
+
]);
|
|
29198
|
+
installResolvedPacks(cfg, record2.id, packResolution);
|
|
28839
29199
|
const selectedBy = principal ? principalSubject3(principal) : void 0;
|
|
28840
|
-
|
|
29200
|
+
const selection = resolvedSelection(request, policy, selectedBy);
|
|
29201
|
+
recordProjectProfileSelection(record2.rootPath, selection);
|
|
29202
|
+
const catalog = executeApprovedListProjectProfiles(cfg, record2.id);
|
|
29203
|
+
const appliedProfileId = firstApplicableProfileId(selection.profileIds, catalog);
|
|
29204
|
+
let appliedSource;
|
|
29205
|
+
if (appliedProfileId) {
|
|
29206
|
+
const application = executeApprovedEnsureProfileInstalled(cfg, record2.id, appliedProfileId);
|
|
29207
|
+
writeProjectType(record2.rootPath, application.profileId);
|
|
29208
|
+
appliedSource = application.source;
|
|
29209
|
+
}
|
|
29210
|
+
const unapplied = appliedProfileId ? unappliedIds(selection.profileIds, appliedProfileId) : [...selection.profileIds];
|
|
28841
29211
|
const actor = principal ? principalSubject3(principal) : SYSTEM_SUBJECT;
|
|
28842
29212
|
tryAppendAudit2(
|
|
28843
29213
|
cfg,
|
|
@@ -28846,7 +29216,18 @@ function performInit(cfg, request, principal) {
|
|
|
28846
29216
|
"project.init.policy",
|
|
28847
29217
|
"info",
|
|
28848
29218
|
"project",
|
|
28849
|
-
{
|
|
29219
|
+
{
|
|
29220
|
+
target: record2.id,
|
|
29221
|
+
projectId: record2.id,
|
|
29222
|
+
metadata: JSON.stringify({
|
|
29223
|
+
...appliedProfileId ? { appliedProfileId, profileSource: appliedSource } : {
|
|
29224
|
+
appliedProfileId: null,
|
|
29225
|
+
profileNotApplied: selection.profileIds.length === 0 ? "no profile was selected \u2014 the default projectType stands" : "no selected profile is resolvable on this instance \u2014 the default projectType stands"
|
|
29226
|
+
},
|
|
29227
|
+
...unapplied.length > 0 ? { unappliedProfileIds: unapplied } : {},
|
|
29228
|
+
...packResolution.unresolved.length > 0 ? { unresolvedPacks: packResolution.unresolved } : {}
|
|
29229
|
+
})
|
|
29230
|
+
},
|
|
28850
29231
|
principal?.tokenId
|
|
28851
29232
|
)
|
|
28852
29233
|
);
|
|
@@ -28887,6 +29268,7 @@ function evaluateProjectPolicy(cfg, credential, projectId) {
|
|
|
28887
29268
|
const root = resolveProjectRoot(cfg.dataDir, principal, projectId);
|
|
28888
29269
|
if (!root) throw new Error(`Unknown project "${projectId}".`);
|
|
28889
29270
|
const policy = effectivePolicy(cfg.dataDir);
|
|
29271
|
+
const governingProfileId = readProjectType(root);
|
|
28890
29272
|
const selection = readProjectProfileSelection(root);
|
|
28891
29273
|
return buildEvaluation({
|
|
28892
29274
|
policy,
|
|
@@ -28894,7 +29276,8 @@ function evaluateProjectPolicy(cfg, credential, projectId) {
|
|
|
28894
29276
|
selectedProfileIds: selection?.profileIds ?? [],
|
|
28895
29277
|
hasSelection: !!selection,
|
|
28896
29278
|
countMissingPacksAsViolation: true,
|
|
28897
|
-
requiredDefaultResolution: executeApprovedResolveGlobalPacks(requiredDefaultNames(policy))
|
|
29279
|
+
requiredDefaultResolution: executeApprovedResolveGlobalPacks(requiredDefaultNames(policy)),
|
|
29280
|
+
governingProfileId
|
|
28898
29281
|
});
|
|
28899
29282
|
}
|
|
28900
29283
|
function reconcileProjectPolicy(cfg, credential, projectId) {
|
|
@@ -28909,20 +29292,41 @@ function reconcileProjectPolicy(cfg, credential, projectId) {
|
|
|
28909
29292
|
const policy = effectivePolicy(cfg.dataDir);
|
|
28910
29293
|
const selection = readProjectProfileSelection(root);
|
|
28911
29294
|
const resolution = executeApprovedResolveGlobalPacks(requiredDefaultNames(policy));
|
|
28912
|
-
|
|
28913
|
-
const installedSet = new Set(installed);
|
|
29295
|
+
const installedSet = new Set(installedPackNames(cfg, projectId));
|
|
28914
29296
|
const toApply = resolution.resolved.filter((p) => !installedSet.has(p.name));
|
|
29297
|
+
const appliedPackNames = toApply.map((p) => p.name);
|
|
28915
29298
|
if (toApply.length > 0) {
|
|
28916
29299
|
installResolvedPacks(cfg, projectId, { resolved: toApply, unresolved: [] });
|
|
28917
|
-
|
|
29300
|
+
}
|
|
29301
|
+
const catalog = executeApprovedListProjectProfiles(cfg, projectId);
|
|
29302
|
+
const previousProfileId = readProjectType(root);
|
|
29303
|
+
let governingProfileId = previousProfileId;
|
|
29304
|
+
const requiredProfileIds = policy.requiredProfileIds ?? [];
|
|
29305
|
+
const policyUnsatisfied = requiredProfileIds.length > 0 && !requiredProfileIds.includes(governingProfileId);
|
|
29306
|
+
const governingUnresolvable = !classifyProfile(governingProfileId, catalog).resolvable;
|
|
29307
|
+
let repairedProfileId;
|
|
29308
|
+
let selectedProfileIds = selection?.profileIds ?? [];
|
|
29309
|
+
if (policyUnsatisfied || governingUnresolvable) {
|
|
29310
|
+
const target = firstApplicableProfileId(
|
|
29311
|
+
[...requiredProfileIds, ...selection?.profileIds ?? []],
|
|
29312
|
+
catalog
|
|
29313
|
+
);
|
|
29314
|
+
if (target) {
|
|
29315
|
+
const application = executeApprovedEnsureProfileInstalled(cfg, projectId, target);
|
|
29316
|
+
writeProjectType(root, application.profileId);
|
|
29317
|
+
governingProfileId = application.profileId;
|
|
29318
|
+
repairedProfileId = application.profileId;
|
|
29319
|
+
selectedProfileIds = foldAppliedProfile(root, application.profileId, principalSubject3(principal)).profileIds;
|
|
29320
|
+
}
|
|
28918
29321
|
}
|
|
28919
29322
|
const result = buildEvaluation({
|
|
28920
29323
|
policy,
|
|
28921
|
-
presentPackNames:
|
|
28922
|
-
selectedProfileIds
|
|
29324
|
+
presentPackNames: installedPackNames(cfg, projectId),
|
|
29325
|
+
selectedProfileIds,
|
|
28923
29326
|
hasSelection: !!selection,
|
|
28924
29327
|
countMissingPacksAsViolation: true,
|
|
28925
|
-
requiredDefaultResolution: resolution
|
|
29328
|
+
requiredDefaultResolution: resolution,
|
|
29329
|
+
governingProfileId
|
|
28926
29330
|
});
|
|
28927
29331
|
tryAppendAudit2(
|
|
28928
29332
|
cfg,
|
|
@@ -28931,7 +29335,15 @@ function reconcileProjectPolicy(cfg, credential, projectId) {
|
|
|
28931
29335
|
"policy.reconcile",
|
|
28932
29336
|
"info",
|
|
28933
29337
|
"policy",
|
|
28934
|
-
{
|
|
29338
|
+
{
|
|
29339
|
+
target: projectId,
|
|
29340
|
+
projectId,
|
|
29341
|
+
metadata: JSON.stringify({
|
|
29342
|
+
...appliedPackNames.length > 0 ? { appliedPackNames } : {},
|
|
29343
|
+
...repairedProfileId ? { repairedProfileId, previousProfileId } : {},
|
|
29344
|
+
...resolution.unresolved.length > 0 ? { unresolvedPacks: resolution.unresolved } : {}
|
|
29345
|
+
})
|
|
29346
|
+
},
|
|
28935
29347
|
principal.tokenId
|
|
28936
29348
|
)
|
|
28937
29349
|
);
|
|
@@ -28947,8 +29359,19 @@ function getProjectConfig(cfg, credential, projectId) {
|
|
|
28947
29359
|
const root = resolveProjectRoot(cfg.dataDir, principal, projectId);
|
|
28948
29360
|
if (!root) throw new Error(`Unknown project "${projectId}".`);
|
|
28949
29361
|
const projectType = readProjectType(root);
|
|
29362
|
+
const selection = readProjectProfileSelection(root);
|
|
29363
|
+
const classified = classifyProfile(projectType, executeApprovedListProjectProfiles(cfg, projectId));
|
|
28950
29364
|
const locked = runWithProjectRoot(root, () => hostCore.readLockRecord() !== null);
|
|
28951
|
-
|
|
29365
|
+
const overriding = overridingSubsystemIds(root);
|
|
29366
|
+
const view = {
|
|
29367
|
+
projectType,
|
|
29368
|
+
locked,
|
|
29369
|
+
profileResolvable: classified.resolvable,
|
|
29370
|
+
unappliedProfileIds: unappliedIds(selection?.profileIds ?? [], projectType),
|
|
29371
|
+
overridingSubsystemIds: overriding
|
|
29372
|
+
};
|
|
29373
|
+
if (classified.source) view.profileSource = classified.source;
|
|
29374
|
+
return view;
|
|
28952
29375
|
}
|
|
28953
29376
|
function setProjectType(cfg, credential, projectId, projectType) {
|
|
28954
29377
|
const principal = requirePrincipal4(cfg, credential);
|
|
@@ -28959,9 +29382,23 @@ function setProjectType(cfg, credential, projectId, projectType) {
|
|
|
28959
29382
|
}
|
|
28960
29383
|
const root = resolveProjectRoot(cfg.dataDir, principal, projectId);
|
|
28961
29384
|
if (!root) throw new Error(`Unknown project "${projectId}".`);
|
|
28962
|
-
|
|
29385
|
+
const application = executeApprovedEnsureProfileInstalled(cfg, projectId, projectType);
|
|
29386
|
+
writeProjectType(root, application.profileId);
|
|
29387
|
+
const folded = foldAppliedProfile(root, application.profileId, principalSubject3(principal));
|
|
29388
|
+
const remainder = folded.profileIds.slice(1);
|
|
28963
29389
|
const locked = runWithProjectRoot(root, () => hostCore.readLockRecord() !== null);
|
|
28964
|
-
|
|
29390
|
+
const overriding = overridingSubsystemIds(root);
|
|
29391
|
+
const view = {
|
|
29392
|
+
projectType: application.profileId,
|
|
29393
|
+
locked,
|
|
29394
|
+
profileSource: application.source,
|
|
29395
|
+
// The write path guarantees resolvability — the ensure seam refused anything else.
|
|
29396
|
+
profileResolvable: true,
|
|
29397
|
+
unappliedProfileIds: remainder,
|
|
29398
|
+
overridingSubsystemIds: overriding
|
|
29399
|
+
};
|
|
29400
|
+
if (application.adoptedPackName) view.adoptedPackName = application.adoptedPackName;
|
|
29401
|
+
return view;
|
|
28965
29402
|
}
|
|
28966
29403
|
function getPackPolicy(cfg, credential) {
|
|
28967
29404
|
requirePrincipal4(cfg, credential);
|
|
@@ -29131,11 +29568,8 @@ function mintToken(cfg, credential, request) {
|
|
|
29131
29568
|
}
|
|
29132
29569
|
assertNotReservedSubjectId(cfg, [request.ownerUserId]);
|
|
29133
29570
|
const projects = request.projects?.length ? request.projects : ["*"];
|
|
29134
|
-
const knownProjects = new Set(listProjectRecords(cfg.dataDir).map((p) => p.id));
|
|
29135
29571
|
for (const p of projects) {
|
|
29136
|
-
|
|
29137
|
-
throw new Error(`unknown project "${p}"`);
|
|
29138
|
-
}
|
|
29572
|
+
assertMintableNarrowingEntry(cfg.dataDir, p);
|
|
29139
29573
|
}
|
|
29140
29574
|
const owner = findUserByRecordOrSubjectId(cfg.dataDir, request.ownerUserId);
|
|
29141
29575
|
if (owner && owner.status !== "active") {
|
|
@@ -29172,12 +29606,21 @@ function revokeToken(cfg, credential, tokenId) {
|
|
|
29172
29606
|
}
|
|
29173
29607
|
function mintSelfToken(cfg, credential, projectId, write) {
|
|
29174
29608
|
const principal = requirePrincipal5(cfg, credential);
|
|
29175
|
-
|
|
29609
|
+
const parsed = parseQualifiedSelector(projectId);
|
|
29610
|
+
if (!parsed) {
|
|
29611
|
+
throw new Error(
|
|
29612
|
+
`invalid project id "${projectId}" (expected a project id, optionally subproject-qualified as projectId::subsystemId)`
|
|
29613
|
+
);
|
|
29614
|
+
}
|
|
29615
|
+
if (authorize(cfg.dataDir, principal, PROJECT_READ_CAPABILITY2, "project", parsed.projectId).value !== "yes") {
|
|
29176
29616
|
throw new ForbiddenError("caller lacks project:read on the requested project");
|
|
29177
29617
|
}
|
|
29178
|
-
if (write && authorize(cfg.dataDir, principal, PROJECT_WRITE_CAPABILITY2, "project", projectId).value !== "yes") {
|
|
29618
|
+
if (write && authorize(cfg.dataDir, principal, PROJECT_WRITE_CAPABILITY2, "project", parsed.projectId).value !== "yes") {
|
|
29179
29619
|
throw new ForbiddenError("caller lacks project:write on the requested project");
|
|
29180
29620
|
}
|
|
29621
|
+
if (parsed.mounts.length > 0) {
|
|
29622
|
+
assertMintableNarrowingEntry(cfg.dataDir, projectId);
|
|
29623
|
+
}
|
|
29181
29624
|
const token = "wk_" + crypto10.randomBytes(24).toString("hex");
|
|
29182
29625
|
const owner = auditActor(principal);
|
|
29183
29626
|
const record2 = {
|
|
@@ -29789,12 +30232,12 @@ function resolveVisibility(observerProjectId, units, placements) {
|
|
|
29789
30232
|
const best = /* @__PURE__ */ new Map();
|
|
29790
30233
|
for (const placement of placements) {
|
|
29791
30234
|
if (placement.projectId === observerProjectId) continue;
|
|
29792
|
-
const
|
|
29793
|
-
if (!
|
|
29794
|
-
const closedOk =
|
|
30235
|
+
const path61 = chainOf(placement.unitId, unitById);
|
|
30236
|
+
if (!path61.length) continue;
|
|
30237
|
+
const closedOk = path61.every((u) => effectivePosture(u, unitById) !== "closed" || observerInside(u.id) || grantedTo(u));
|
|
29795
30238
|
if (!closedOk) continue;
|
|
29796
|
-
const crossTenant = !tenantRoots.has(
|
|
29797
|
-
if (crossTenant && !
|
|
30239
|
+
const crossTenant = !tenantRoots.has(path61[path61.length - 1].id);
|
|
30240
|
+
if (crossTenant && !path61.some(grantedTo)) continue;
|
|
29798
30241
|
if (crossTenant && directUnits.length === 0) continue;
|
|
29799
30242
|
const distance = crossTenant ? "partner" : sameBranch(placement.unitId) ? "department" : "instance";
|
|
29800
30243
|
const existing = best.get(placement.projectId);
|
|
@@ -30733,10 +31176,9 @@ function migratePermissionModel(dataDir, apply) {
|
|
|
30733
31176
|
// src/server/http.ts
|
|
30734
31177
|
var http2 = __toESM(require("http"));
|
|
30735
31178
|
var fs49 = __toESM(require("fs"));
|
|
30736
|
-
var
|
|
31179
|
+
var path58 = __toESM(require("path"));
|
|
30737
31180
|
|
|
30738
31181
|
// src/server/request.ts
|
|
30739
|
-
var path58 = __toESM(require("path"));
|
|
30740
31182
|
var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
30741
31183
|
init_fs();
|
|
30742
31184
|
|
|
@@ -31950,6 +32392,9 @@ function getProjectConfig2(cfg, credential, projectId) {
|
|
|
31950
32392
|
function setProjectType2(cfg, credential, projectId, projectType) {
|
|
31951
32393
|
return setProjectType(cfg, credential, projectId, projectType);
|
|
31952
32394
|
}
|
|
32395
|
+
function listProjectProfiles2(cfg, credential, project2) {
|
|
32396
|
+
return listProjectProfiles(cfg, credential, project2);
|
|
32397
|
+
}
|
|
31953
32398
|
function listProducers2(cfg, credential, project2) {
|
|
31954
32399
|
return listProducers(cfg, credential, project2);
|
|
31955
32400
|
}
|
|
@@ -35311,6 +35756,9 @@ function opsGetProjectConfig(cfg, sessionId, url, res) {
|
|
|
35311
35756
|
function opsSetProjectConfig(cfg, sessionId, body, res) {
|
|
35312
35757
|
sendJson(res, 200, setProjectType2(cfg, sessionId, String(body?.projectId ?? ""), String(body?.projectType ?? "")));
|
|
35313
35758
|
}
|
|
35759
|
+
function opsListProjectProfiles(cfg, sessionId, url, res) {
|
|
35760
|
+
sendJson(res, 200, { profiles: listProjectProfiles2(cfg, sessionId, q(url, "projectId") ?? "") });
|
|
35761
|
+
}
|
|
35314
35762
|
function opsListProducers(cfg, sessionId, url, res) {
|
|
35315
35763
|
sendJson(res, 200, { producers: listProducers2(cfg, sessionId, q(url, "projectId") ?? "") });
|
|
35316
35764
|
}
|
|
@@ -35550,6 +35998,9 @@ async function handleWebRequest(cfg, req, res, body, url, ctx) {
|
|
|
35550
35998
|
if (req.method === "POST" && parts.length === 3 && parts[2] === "config") {
|
|
35551
35999
|
return opsSetProjectConfig(cfg, sessionId, body, res);
|
|
35552
36000
|
}
|
|
36001
|
+
if (req.method === "GET" && parts.length === 3 && parts[2] === "profiles") {
|
|
36002
|
+
return opsListProjectProfiles(cfg, sessionId, url, res);
|
|
36003
|
+
}
|
|
35553
36004
|
if (req.method === "GET" && parts.length === 3 && parts[2] === "policy") {
|
|
35554
36005
|
return opsPolicyEvaluate(cfg, sessionId, url, res);
|
|
35555
36006
|
}
|
|
@@ -35774,8 +36225,8 @@ var RealtimeHub = class {
|
|
|
35774
36225
|
* complete the handshake, and register the connection. A bad path or session
|
|
35775
36226
|
* destroys the socket. */
|
|
35776
36227
|
handleUpgrade(cfg, req, socket) {
|
|
35777
|
-
const
|
|
35778
|
-
if (
|
|
36228
|
+
const path61 = (req.url ?? "/").split("?")[0];
|
|
36229
|
+
if (path61 !== REALTIME_PATH || !isWebSocketUpgrade(req)) {
|
|
35779
36230
|
socket.destroy();
|
|
35780
36231
|
return;
|
|
35781
36232
|
}
|
|
@@ -35920,7 +36371,7 @@ function deriveMcpOutcome(response) {
|
|
|
35920
36371
|
if (r.result && typeof r.result === "object" && r.result.isError) return "failed";
|
|
35921
36372
|
return "success";
|
|
35922
36373
|
}
|
|
35923
|
-
function auditToolCall(dataDir, principal, projectId, body, outcome) {
|
|
36374
|
+
function auditToolCall(dataDir, principal, projectId, body, outcome, subproject) {
|
|
35924
36375
|
const target = mcpToolTarget(body);
|
|
35925
36376
|
if (!target) return;
|
|
35926
36377
|
const actor = principal.subject ?? {
|
|
@@ -35938,7 +36389,8 @@ function auditToolCall(dataDir, principal, projectId, body, outcome) {
|
|
|
35938
36389
|
actor,
|
|
35939
36390
|
tokenId: principal.tokenId,
|
|
35940
36391
|
projectId,
|
|
35941
|
-
target
|
|
36392
|
+
target,
|
|
36393
|
+
...subproject ? { metadata: JSON.stringify({ subproject }) } : {}
|
|
35942
36394
|
};
|
|
35943
36395
|
try {
|
|
35944
36396
|
appendAuditEvent(dataDir, event, DEFAULT_AUDIT_POLICY);
|
|
@@ -36142,8 +36594,8 @@ async function handleMcpRequest(cfg, req, res, body, credential) {
|
|
|
36142
36594
|
permissionSubject: { subjectId: "anonymous", roleBindings: [], instanceAdmin: true }
|
|
36143
36595
|
};
|
|
36144
36596
|
}
|
|
36145
|
-
const
|
|
36146
|
-
if (!
|
|
36597
|
+
const binding = resolveProjectBinding(cfg.dataDir, principal, projectSelector(req));
|
|
36598
|
+
if (!binding) {
|
|
36147
36599
|
sendJson(res, 403, { error: "project not authorized, unknown, or not specified" });
|
|
36148
36600
|
return;
|
|
36149
36601
|
}
|
|
@@ -36158,19 +36610,20 @@ async function handleMcpRequest(cfg, req, res, body, credential) {
|
|
|
36158
36610
|
});
|
|
36159
36611
|
return;
|
|
36160
36612
|
}
|
|
36161
|
-
await runWithProjectRoot(
|
|
36162
|
-
const projectId =
|
|
36613
|
+
await runWithProjectRoot(binding.rootPath, async () => {
|
|
36614
|
+
const projectId = binding.projectId;
|
|
36615
|
+
const subproject = binding.subproject;
|
|
36163
36616
|
const dispatchedResponse = await dispatchProjectLifecycleTool(cfg, cred, projectId, body);
|
|
36164
36617
|
if (dispatchedResponse !== void 0) {
|
|
36165
36618
|
sendJson(res, 200, dispatchedResponse);
|
|
36166
|
-
auditToolCall(cfg.dataDir, principal, projectId, body, deriveMcpOutcome(dispatchedResponse));
|
|
36619
|
+
auditToolCall(cfg.dataDir, principal, projectId, body, deriveMcpOutcome(dispatchedResponse), subproject);
|
|
36167
36620
|
for (const ch of mcpChangeChannels(body, projectId, dispatchedResponse)) publishChange(ch);
|
|
36168
36621
|
return;
|
|
36169
36622
|
}
|
|
36170
36623
|
const permissionError = dataPlanePermissionError(cfg, principal, projectId, body);
|
|
36171
36624
|
if (permissionError !== void 0) {
|
|
36172
36625
|
sendJson(res, 200, permissionError);
|
|
36173
|
-
auditToolCall(cfg.dataDir, principal, projectId, body, deriveMcpOutcome(permissionError));
|
|
36626
|
+
auditToolCall(cfg.dataDir, principal, projectId, body, deriveMcpOutcome(permissionError), subproject);
|
|
36174
36627
|
return;
|
|
36175
36628
|
}
|
|
36176
36629
|
const server = createScopedServer();
|
|
@@ -36191,7 +36644,7 @@ async function handleMcpRequest(cfg, req, res, body, credential) {
|
|
|
36191
36644
|
};
|
|
36192
36645
|
await server.connect(transport);
|
|
36193
36646
|
await transport.handleRequest(req, res, body);
|
|
36194
|
-
auditToolCall(cfg.dataDir, principal, projectId, body, deriveMcpOutcome(response));
|
|
36647
|
+
auditToolCall(cfg.dataDir, principal, projectId, body, deriveMcpOutcome(response), subproject);
|
|
36195
36648
|
for (const ch of mcpChangeChannels(body, projectId, response)) publishChange(ch);
|
|
36196
36649
|
});
|
|
36197
36650
|
}
|
|
@@ -36626,7 +37079,7 @@ function routeData(cfg, req, res) {
|
|
|
36626
37079
|
}
|
|
36627
37080
|
function readExposurePolicyFile(dataDir) {
|
|
36628
37081
|
try {
|
|
36629
|
-
const raw = fs49.readFileSync(
|
|
37082
|
+
const raw = fs49.readFileSync(path58.join(dataDir, "exposure-policy.json"), "utf8");
|
|
36630
37083
|
const parsed = JSON.parse(raw);
|
|
36631
37084
|
return parsed && typeof parsed === "object" ? parsed : void 0;
|
|
36632
37085
|
} catch {
|
|
@@ -37311,9 +37764,9 @@ function seedDemoTree() {
|
|
|
37311
37764
|
|
|
37312
37765
|
// src/commands/host.ts
|
|
37313
37766
|
function resolveHostConfig(options) {
|
|
37314
|
-
const dataDir = options.dataDir || process.env["WAIRON_DATA_DIR"] ||
|
|
37767
|
+
const dataDir = options.dataDir || process.env["WAIRON_DATA_DIR"] || path59.join(os10.homedir(), ".wairon", "data");
|
|
37315
37768
|
if (!process.env["WAIRON_PACKS_DIR"]) {
|
|
37316
|
-
process.env["WAIRON_PACKS_DIR"] =
|
|
37769
|
+
process.env["WAIRON_PACKS_DIR"] = path59.join(dataDir, "packs");
|
|
37317
37770
|
}
|
|
37318
37771
|
const cfg = {
|
|
37319
37772
|
host: options.host || "0.0.0.0",
|
|
@@ -37438,13 +37891,13 @@ function openBrowser(url) {
|
|
|
37438
37891
|
}
|
|
37439
37892
|
async function runDev(options = {}) {
|
|
37440
37893
|
const cwd = process.cwd();
|
|
37441
|
-
if (!fs50.existsSync(
|
|
37894
|
+
if (!fs50.existsSync(path59.join(cwd, ".wai"))) {
|
|
37442
37895
|
throw new WaironError(
|
|
37443
37896
|
"No .wai/ found in the current directory. Run `wairon dev` from a wairon project root (or run `wairon init` first)."
|
|
37444
37897
|
);
|
|
37445
37898
|
}
|
|
37446
37899
|
const hash = crypto20.createHash("sha256").update(cwd).digest("hex").slice(0, 16);
|
|
37447
|
-
const dataDir =
|
|
37900
|
+
const dataDir = path59.join(os10.tmpdir(), "wairon-dev", hash);
|
|
37448
37901
|
fs50.mkdirSync(dataDir, { recursive: true });
|
|
37449
37902
|
registerLocalDevProject(dataDir, "local", cwd);
|
|
37450
37903
|
const port = options.port ? Number(options.port) : 8080;
|
|
@@ -37853,8 +38306,8 @@ async function runHostPacks(action, options = {}) {
|
|
|
37853
38306
|
}
|
|
37854
38307
|
case "install": {
|
|
37855
38308
|
if (!options.file) throw new WaironError("`--file <path>` (a declarative pack YAML) is required for install.");
|
|
37856
|
-
const name = options.name ??
|
|
37857
|
-
const content = fs50.readFileSync(
|
|
38309
|
+
const name = options.name ?? path59.basename(options.file).replace(/\.(ya?ml)$/i, "");
|
|
38310
|
+
const content = fs50.readFileSync(path59.resolve(options.file), "utf8");
|
|
37858
38311
|
const desc = project2 ? installProjectPack(cfg, cred, project2, name, content) : installGlobalPack(cfg, cred, name, content);
|
|
37859
38312
|
logger.success(`Installed ${scope} pack "${desc.name}" (${desc.profiles} profile(s), ${desc.languages} language(s)).`);
|
|
37860
38313
|
if (project2) logger.info("Committed with the project \u2014 every clone and CI will enforce it.");
|
|
@@ -38060,13 +38513,27 @@ async function runSurface(action, options = {}) {
|
|
|
38060
38513
|
for (const p of written) logger.info(` ${p}`);
|
|
38061
38514
|
return;
|
|
38062
38515
|
}
|
|
38516
|
+
case "externals": {
|
|
38517
|
+
const entries = listExternalInterfaces();
|
|
38518
|
+
if (!entries.length) {
|
|
38519
|
+
logger.info("No external surfaces available (.wai/surfaces/ holds no snapshots).");
|
|
38520
|
+
return;
|
|
38521
|
+
}
|
|
38522
|
+
const freshness = (f) => f === "fresh" ? import_chalk19.default.green(f) : f === "stale" ? import_chalk19.default.yellow(f) : import_chalk19.default.gray(f);
|
|
38523
|
+
for (const e of entries) {
|
|
38524
|
+
logger.info(
|
|
38525
|
+
`${e.sourceKind.padEnd(8)} ${import_chalk19.default.cyan(e.projectName)} [${e.origin}] ${freshness(e.freshness)} \u2014 ${e.interfaceIds.length ? e.interfaceIds.join(", ") : "(no interfaces)"}`
|
|
38526
|
+
);
|
|
38527
|
+
}
|
|
38528
|
+
return;
|
|
38529
|
+
}
|
|
38063
38530
|
default:
|
|
38064
|
-
throw new WaironError(`Unknown surface action "${action}" (supported: export, import, list, generate-children).`);
|
|
38531
|
+
throw new WaironError(`Unknown surface action "${action}" (supported: export, import, list, generate-children, externals).`);
|
|
38065
38532
|
}
|
|
38066
38533
|
}
|
|
38067
38534
|
|
|
38068
38535
|
// src/commands/subsystem.ts
|
|
38069
|
-
var
|
|
38536
|
+
var path60 = __toESM(require("path"));
|
|
38070
38537
|
init_logger();
|
|
38071
38538
|
init_errors();
|
|
38072
38539
|
init_fs();
|
|
@@ -38102,9 +38569,9 @@ async function runSubsystemAdd(id, options = {}) {
|
|
|
38102
38569
|
updatedAt: now
|
|
38103
38570
|
};
|
|
38104
38571
|
createChainedSubsystem(subsystem, displayName);
|
|
38105
|
-
const childDir =
|
|
38572
|
+
const childDir = path60.resolve(getProjectRoot(), options.projectPath);
|
|
38106
38573
|
logger.success(`Added external subsystem "${id}" \u2192 ${options.projectPath}`);
|
|
38107
|
-
logger.info(`Scaffolded child project at ${
|
|
38574
|
+
logger.info(`Scaffolded child project at ${path60.relative(process.cwd(), childDir) || "."}`);
|
|
38108
38575
|
logger.info(`Design its spec tree from this parent using namespaced ids (e.g. ${id}::<component>).`);
|
|
38109
38576
|
}
|
|
38110
38577
|
async function runSubsystemMove(id, options = {}) {
|
|
@@ -38127,9 +38594,9 @@ async function runSubsystemExternalize(id, options = {}) {
|
|
|
38127
38594
|
throw new WaironError("--project-path (the subproject destination) is required.");
|
|
38128
38595
|
}
|
|
38129
38596
|
externalizeSubsystem(id, options.projectPath);
|
|
38130
|
-
const childDir =
|
|
38597
|
+
const childDir = path60.resolve(getProjectRoot(), options.projectPath);
|
|
38131
38598
|
logger.success(`Externalized subsystem "${id}" \u2192 ${options.projectPath}`);
|
|
38132
|
-
logger.info(`Moved its specs into ${
|
|
38599
|
+
logger.info(`Moved its specs into ${path60.relative(process.cwd(), childDir) || "."} (now a standalone subproject).`);
|
|
38133
38600
|
logger.info("Move the source code there yourself, then run `wairon validate` to confirm the tree.");
|
|
38134
38601
|
}
|
|
38135
38602
|
async function runSubsystemInternalize(id) {
|
|
@@ -38168,8 +38635,64 @@ program.command("generate").description("Generate agent output files from the sp
|
|
|
38168
38635
|
dryRun: opts.dryRun
|
|
38169
38636
|
});
|
|
38170
38637
|
});
|
|
38638
|
+
async function runLock2(options) {
|
|
38639
|
+
assertProjectInitialized();
|
|
38640
|
+
if (!pathExists(AI_PATHS.specsSystem())) {
|
|
38641
|
+
logger.error("No SDD spec tree found (.wai/specs). Nothing to lock.");
|
|
38642
|
+
process.exit(1);
|
|
38643
|
+
}
|
|
38644
|
+
const projectConfig = loadProjectConfig();
|
|
38645
|
+
logger.info("Analyzing and validating specifications in-memory...");
|
|
38646
|
+
const dry = validateAsComplete({
|
|
38647
|
+
rules: projectConfig.rules,
|
|
38648
|
+
projectType: projectConfig.projectType,
|
|
38649
|
+
scopeSubsystem: options.subsystem,
|
|
38650
|
+
recursive: options.recursive ?? true
|
|
38651
|
+
});
|
|
38652
|
+
const errors = dry.issues.filter((i) => i.severity === "error");
|
|
38653
|
+
if (errors.length > 0) {
|
|
38654
|
+
logger.header("Cannot lock \u2014 the spec tree does not validate as complete");
|
|
38655
|
+
let errorCount = 0;
|
|
38656
|
+
const MAX_PRINT = 100;
|
|
38657
|
+
let skippedErrors = 0;
|
|
38658
|
+
for (const i of errors) {
|
|
38659
|
+
if (errorCount < MAX_PRINT) {
|
|
38660
|
+
logger.error(`${i.specId ? `[${i.specId}] ` : ""}[${i.code}] ${i.message}`);
|
|
38661
|
+
errorCount++;
|
|
38662
|
+
} else {
|
|
38663
|
+
skippedErrors++;
|
|
38664
|
+
}
|
|
38665
|
+
}
|
|
38666
|
+
if (skippedErrors > 0) {
|
|
38667
|
+
logger.error(`... and ${skippedErrors} more error(s) omitted.`);
|
|
38668
|
+
}
|
|
38669
|
+
logger.blank();
|
|
38670
|
+
logger.info("Fix the errors above, then run `wairon lock` again. Nothing was changed.");
|
|
38671
|
+
process.exit(1);
|
|
38672
|
+
}
|
|
38673
|
+
logger.header("Lock SDD specs");
|
|
38674
|
+
const record2 = await runLock(options, dry);
|
|
38675
|
+
if (!record2) {
|
|
38676
|
+
logger.info("Cancelled. Nothing was changed.");
|
|
38677
|
+
return;
|
|
38678
|
+
}
|
|
38679
|
+
const childPaths = generateChildSnapshots();
|
|
38680
|
+
if (childPaths.length > 0) {
|
|
38681
|
+
logger.blank();
|
|
38682
|
+
logger.success(`Regenerated the family/sibling surfaces into ${childPaths.length} chained child snapshot(s):`);
|
|
38683
|
+
for (const p of childPaths) logger.info(` ${p}`);
|
|
38684
|
+
}
|
|
38685
|
+
logger.blank();
|
|
38686
|
+
await runGenerate({ domain: options.subsystem });
|
|
38687
|
+
logger.blank();
|
|
38688
|
+
logger.success("Specs locked and agent topology generated.");
|
|
38689
|
+
logger.info(`Lock record written (.wai/lock.json): stateId ${record2.stateId.algorithm}:${record2.stateId.digest} \u2014 status ${record2.status}.`);
|
|
38690
|
+
logger.warn(
|
|
38691
|
+
"Restart any running AI agent sessions (Claude Code / Antigravity / Codex) so the newly generated implementer agents load \u2014 they are not picked up mid-session."
|
|
38692
|
+
);
|
|
38693
|
+
}
|
|
38171
38694
|
program.command("lock").description("Final check before implementation: validate the spec tree as complete, freeze all specs to complete, and (re)generate the agent topology \u2014 only if it validates").option("-y, --yes", "skip the confirmation prompt (for scripts / CI)").option("--subsystem <id>", "only lock specs in the specified subsystem").option("--no-recursive", "do not recursively validate subprojects").action(async (opts) => {
|
|
38172
|
-
await
|
|
38695
|
+
await runLock2({ yes: opts.yes, subsystem: opts.subsystem, recursive: opts.recursive });
|
|
38173
38696
|
});
|
|
38174
38697
|
program.command("validate").description("Validate the project configuration and the SDD Spec Tree").option("--ci", "treat warnings as errors for CI pipelines").option("--subsystem <id>", "only validate the specified subsystem (granular)").option("--no-recursive", "do not recursively validate subprojects").action(async (opts) => {
|
|
38175
38698
|
await runValidate({ ci: opts.ci, subsystem: opts.subsystem, recursive: opts.recursive });
|
|
@@ -38285,7 +38808,7 @@ mcpCmd.command("status").description("Show whether the wairon MCP server is regi
|
|
|
38285
38808
|
program.command("produce <target>").description("project the local project's specs to a producer target (notion | miro)").option("--page <id>", "parent page/board id in the target").option("--token <token>", "integration token (else env, else interactive prompt)").action(async (target, opts) => {
|
|
38286
38809
|
await runProduce(target, { page: opts.page, token: opts.token });
|
|
38287
38810
|
});
|
|
38288
|
-
program.command("surface <action>").description("public surface exchange: export | import | list | generate-children").option("--audience <level>", "export ceiling: project | department | instance | partner | external (default instance)").option("--format <fmt>", "export format: native | openapi (default native)").option("--out <path>", "export output path (else print)").option("--portal <id>", "export: select one portal's OpenAPI spec (a multi-portal project renders one document per portal)").option("--source <path>", "import: the surface document (native snapshot YAML or OpenAPI)").option("--origin <origin>", "import provenance: exchanged | authored (default authored)").action(async (action, opts) => {
|
|
38811
|
+
program.command("surface <action>").description("public surface exchange: export | import | list | generate-children | externals").option("--audience <level>", "export ceiling: project | department | instance | partner | external (default instance)").option("--format <fmt>", "export format: native | openapi (default native)").option("--out <path>", "export output path (else print)").option("--portal <id>", "export: select one portal's OpenAPI spec (a multi-portal project renders one document per portal)").option("--source <path>", "import: the surface document (native snapshot YAML or OpenAPI)").option("--origin <origin>", "import provenance: exchanged | authored (default authored)").action(async (action, opts) => {
|
|
38289
38812
|
await runSurface(action, {
|
|
38290
38813
|
audience: opts.audience,
|
|
38291
38814
|
format: opts.format,
|