arkaik 0.1.0 → 0.1.2
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/assets/bootstrap-skill/references/fragments.md +203 -0
- package/dist/assets/bootstrap-skill/references/waves.md +120 -0
- package/dist/assets/bootstrap-skill/skill.md +190 -0
- package/dist/assets/skill/references/schema.md +76 -3
- package/dist/assets/skill/scripts/validate-bundle.js +4 -4
- package/dist/assets/skill/skill.md +104 -6
- package/dist/index.js +2696 -281
- package/dist/io.js +497 -61
- package/package.json +1 -1
package/dist/io.js
CHANGED
|
@@ -6,33 +6,6 @@ var __export = (target, all) => {
|
|
|
6
6
|
|
|
7
7
|
// src/lib/bundle-io.ts
|
|
8
8
|
import { existsSync, readFileSync } from "node:fs";
|
|
9
|
-
function readBundle(filePath) {
|
|
10
|
-
if (!existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
|
|
11
|
-
let parsed;
|
|
12
|
-
try {
|
|
13
|
-
parsed = JSON.parse(readFileSync(filePath, "utf8"));
|
|
14
|
-
} catch (e) {
|
|
15
|
-
throw new Error(`Cannot parse JSON \u2014 ${e.message}`);
|
|
16
|
-
}
|
|
17
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
18
|
-
throw new Error("Bundle must be a JSON object.");
|
|
19
|
-
}
|
|
20
|
-
return parsed;
|
|
21
|
-
}
|
|
22
|
-
function nodesByIdOf(bundle) {
|
|
23
|
-
const nodes = Array.isArray(bundle.nodes) ? bundle.nodes : [];
|
|
24
|
-
const map2 = /* @__PURE__ */ new Map();
|
|
25
|
-
for (const n of nodes) {
|
|
26
|
-
if (n !== null && typeof n === "object" && typeof n.id === "string") {
|
|
27
|
-
map2.set(n.id, n);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return map2;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// src/lib/journal-io.ts
|
|
34
|
-
import { appendFileSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
35
|
-
import { dirname, join } from "node:path";
|
|
36
9
|
|
|
37
10
|
// ../../node_modules/zod/v4/classic/external.js
|
|
38
11
|
var external_exports = {};
|
|
@@ -14549,19 +14522,58 @@ function date4(params) {
|
|
|
14549
14522
|
config(en_default());
|
|
14550
14523
|
|
|
14551
14524
|
// ../schema/src/ids.ts
|
|
14552
|
-
var SPECIES_IDS = ["flow", "view", "data-model", "api-endpoint", "acceptance"];
|
|
14525
|
+
var SPECIES_IDS = ["flow", "view", "data-model", "api-endpoint", "acceptance", "decision"];
|
|
14553
14526
|
var STATUS_IDS = [
|
|
14554
14527
|
"idea",
|
|
14528
|
+
"discovery",
|
|
14555
14529
|
"backlog",
|
|
14556
|
-
"prioritized",
|
|
14557
14530
|
"development",
|
|
14558
14531
|
"releasing",
|
|
14559
14532
|
"live",
|
|
14560
|
-
"archived"
|
|
14561
|
-
"blocked"
|
|
14533
|
+
"archived"
|
|
14562
14534
|
];
|
|
14563
14535
|
var PLATFORM_IDS = ["web", "ios", "android"];
|
|
14564
|
-
var EDGE_TYPE_IDS = ["composes", "calls", "displays", "queries", "covers"];
|
|
14536
|
+
var EDGE_TYPE_IDS = ["composes", "calls", "displays", "queries", "covers", "supersedes", "generates", "impacts"];
|
|
14537
|
+
var VALID_EDGE_SEMANTICS = {
|
|
14538
|
+
composes: [
|
|
14539
|
+
["flow", "view"],
|
|
14540
|
+
["flow", "flow"],
|
|
14541
|
+
["view", "flow"],
|
|
14542
|
+
["view", "view"]
|
|
14543
|
+
],
|
|
14544
|
+
// `calls` names the initiator, which is why it runs in both directions
|
|
14545
|
+
// between a view and an endpoint: view → api is the outbound/write
|
|
14546
|
+
// affordance, api → view the inbound/read one a server opens itself (a
|
|
14547
|
+
// webhook, an SSE channel, a push). Both project onto the View card
|
|
14548
|
+
// (docs/graph-model.md § Edge Types). Consumers that walk `calls` must
|
|
14549
|
+
// therefore not assume it points down into the system layer — see
|
|
14550
|
+
// `buildProductUsageIndex` in ./products.ts, which restricts hops by
|
|
14551
|
+
// target species for exactly this reason.
|
|
14552
|
+
calls: [
|
|
14553
|
+
["view", "api-endpoint"],
|
|
14554
|
+
["flow", "api-endpoint"],
|
|
14555
|
+
["api-endpoint", "api-endpoint"],
|
|
14556
|
+
["api-endpoint", "view"]
|
|
14557
|
+
],
|
|
14558
|
+
displays: [["view", "data-model"]],
|
|
14559
|
+
queries: [["api-endpoint", "data-model"]],
|
|
14560
|
+
covers: [
|
|
14561
|
+
["acceptance", "view"],
|
|
14562
|
+
["acceptance", "flow"]
|
|
14563
|
+
],
|
|
14564
|
+
// Decision edges (cycle 2). `generates` and `impacts` are deliberately
|
|
14565
|
+
// disjoint: an acceptance is *generated* by a decision, never merely
|
|
14566
|
+
// impacted, so `impacts` does not admit an acceptance target
|
|
14567
|
+
// (docs/superpowers/specs/2026-08-03-decisions-species-design.md §3).
|
|
14568
|
+
supersedes: [["decision", "decision"]],
|
|
14569
|
+
generates: [["decision", "acceptance"]],
|
|
14570
|
+
impacts: [
|
|
14571
|
+
["decision", "flow"],
|
|
14572
|
+
["decision", "view"],
|
|
14573
|
+
["decision", "data-model"],
|
|
14574
|
+
["decision", "api-endpoint"]
|
|
14575
|
+
]
|
|
14576
|
+
};
|
|
14565
14577
|
var VALUE_TIER_IDS = ["functional", "emotional", "life-changing", "social-impact"];
|
|
14566
14578
|
var VALUE_IDS = [
|
|
14567
14579
|
// functional (14)
|
|
@@ -14600,6 +14612,106 @@ var VALUE_IDS = [
|
|
|
14600
14612
|
"self-transcendence"
|
|
14601
14613
|
];
|
|
14602
14614
|
|
|
14615
|
+
// ../schema/src/legacy-status.ts
|
|
14616
|
+
var LEGACY_STATUS_IDS = ["prioritized", "blocked"];
|
|
14617
|
+
var LEGACY_STATUS_ALIASES = {
|
|
14618
|
+
prioritized: "backlog",
|
|
14619
|
+
blocked: "development"
|
|
14620
|
+
};
|
|
14621
|
+
var STATUS_VOCABULARY_VERSION = 3;
|
|
14622
|
+
var BLOCKED_BY_MIGRATION_NOTE = "migrated from legacy blocked status";
|
|
14623
|
+
function normalizeStatus(value) {
|
|
14624
|
+
if (STATUS_IDS.includes(value)) return value;
|
|
14625
|
+
return LEGACY_STATUS_ALIASES[value];
|
|
14626
|
+
}
|
|
14627
|
+
var LEGACY_ALIAS_REMAP = { ...LEGACY_STATUS_ALIASES };
|
|
14628
|
+
var LEGACY_VOCABULARY_REMAP = {
|
|
14629
|
+
backlog: "idea",
|
|
14630
|
+
...LEGACY_STATUS_ALIASES
|
|
14631
|
+
};
|
|
14632
|
+
function migrateNode(node, remap) {
|
|
14633
|
+
let changed = false;
|
|
14634
|
+
let wasBlocked = node.status === "blocked";
|
|
14635
|
+
const mappedStatus = remap[node.status];
|
|
14636
|
+
const status = mappedStatus ?? node.status;
|
|
14637
|
+
if (mappedStatus !== void 0) changed = true;
|
|
14638
|
+
let metadata = node.metadata;
|
|
14639
|
+
const platformStatuses = node.metadata?.platformStatuses;
|
|
14640
|
+
if (platformStatuses !== void 0) {
|
|
14641
|
+
let anyMapped = false;
|
|
14642
|
+
const next = {};
|
|
14643
|
+
for (const [platform, value] of Object.entries(platformStatuses)) {
|
|
14644
|
+
if (value === "blocked") wasBlocked = true;
|
|
14645
|
+
const mapped = remap[value];
|
|
14646
|
+
next[platform] = mapped ?? value;
|
|
14647
|
+
if (mapped !== void 0) anyMapped = true;
|
|
14648
|
+
}
|
|
14649
|
+
if (anyMapped) {
|
|
14650
|
+
metadata = { ...metadata, platformStatuses: next };
|
|
14651
|
+
changed = true;
|
|
14652
|
+
}
|
|
14653
|
+
}
|
|
14654
|
+
const refs = node.metadata?.refs;
|
|
14655
|
+
if (Array.isArray(refs)) {
|
|
14656
|
+
let anyMapped = false;
|
|
14657
|
+
const nextRefs = refs.map((ref) => {
|
|
14658
|
+
if (ref.status_mapped === void 0) return ref;
|
|
14659
|
+
const mapped = remap[ref.status_mapped];
|
|
14660
|
+
if (mapped === void 0) return ref;
|
|
14661
|
+
anyMapped = true;
|
|
14662
|
+
return { ...ref, status_mapped: mapped };
|
|
14663
|
+
});
|
|
14664
|
+
if (anyMapped) {
|
|
14665
|
+
metadata = { ...metadata, refs: nextRefs };
|
|
14666
|
+
changed = true;
|
|
14667
|
+
}
|
|
14668
|
+
}
|
|
14669
|
+
if (wasBlocked && !metadata?.blocked_by) {
|
|
14670
|
+
metadata = { ...metadata, blocked_by: BLOCKED_BY_MIGRATION_NOTE };
|
|
14671
|
+
changed = true;
|
|
14672
|
+
}
|
|
14673
|
+
if (!changed) return node;
|
|
14674
|
+
return { ...node, status, ...metadata !== void 0 ? { metadata } : {} };
|
|
14675
|
+
}
|
|
14676
|
+
function migrateStatusVocabulary(bundle) {
|
|
14677
|
+
const current = typeof bundle.schema_version === "number" && bundle.schema_version >= STATUS_VOCABULARY_VERSION;
|
|
14678
|
+
const remap = current ? LEGACY_ALIAS_REMAP : LEGACY_VOCABULARY_REMAP;
|
|
14679
|
+
const nodes = bundle.nodes.map((node) => migrateNode(node, remap));
|
|
14680
|
+
if (current) {
|
|
14681
|
+
const untouched = nodes.every((node, i) => node === bundle.nodes[i]);
|
|
14682
|
+
return untouched ? bundle : { ...bundle, nodes };
|
|
14683
|
+
}
|
|
14684
|
+
return { ...bundle, schema_version: STATUS_VOCABULARY_VERSION, nodes };
|
|
14685
|
+
}
|
|
14686
|
+
|
|
14687
|
+
// ../schema/src/decision.ts
|
|
14688
|
+
var DECISION_STATUS_IDS = [
|
|
14689
|
+
"proposed",
|
|
14690
|
+
"approved",
|
|
14691
|
+
"enacted",
|
|
14692
|
+
"rejected",
|
|
14693
|
+
"deprecated",
|
|
14694
|
+
"superseded"
|
|
14695
|
+
];
|
|
14696
|
+
function lifecycleStatusForDecision(decisionStatus) {
|
|
14697
|
+
switch (decisionStatus) {
|
|
14698
|
+
case "proposed":
|
|
14699
|
+
return "discovery";
|
|
14700
|
+
case "approved":
|
|
14701
|
+
return "backlog";
|
|
14702
|
+
case "enacted":
|
|
14703
|
+
return "live";
|
|
14704
|
+
case "rejected":
|
|
14705
|
+
case "deprecated":
|
|
14706
|
+
case "superseded":
|
|
14707
|
+
return "archived";
|
|
14708
|
+
}
|
|
14709
|
+
}
|
|
14710
|
+
function decisionStatusOf(node) {
|
|
14711
|
+
const raw = node.metadata?.decision_status;
|
|
14712
|
+
return DECISION_STATUS_IDS.includes(raw) ? raw : "proposed";
|
|
14713
|
+
}
|
|
14714
|
+
|
|
14603
14715
|
// ../schema/src/enums.ts
|
|
14604
14716
|
var SpeciesSchema = external_exports.enum(SPECIES_IDS).meta({
|
|
14605
14717
|
id: "Species",
|
|
@@ -14609,6 +14721,10 @@ var StatusSchema = external_exports.enum(STATUS_IDS).meta({
|
|
|
14609
14721
|
id: "Status",
|
|
14610
14722
|
description: "Lifecycle status of a node."
|
|
14611
14723
|
});
|
|
14724
|
+
var AnyStatusSchema = external_exports.enum([...STATUS_IDS, ...LEGACY_STATUS_IDS]).meta({
|
|
14725
|
+
id: "AnyStatus",
|
|
14726
|
+
description: "Lifecycle status as stored: the current vocabulary, or a legacy id (prioritized, blocked) accepted from pre-v3 bundles and migrated on load."
|
|
14727
|
+
});
|
|
14612
14728
|
var PlatformSchema = external_exports.enum(PLATFORM_IDS).meta({
|
|
14613
14729
|
id: "Platform",
|
|
14614
14730
|
description: "Target platform."
|
|
@@ -14625,6 +14741,10 @@ var ValueSchema = external_exports.enum(VALUE_IDS).meta({
|
|
|
14625
14741
|
id: "Value",
|
|
14626
14742
|
description: "A Bain B2C Elements-of-Value element served by an acceptance (spec \xA73.2)."
|
|
14627
14743
|
});
|
|
14744
|
+
var DecisionStatusSchema = external_exports.enum(DECISION_STATUS_IDS).meta({
|
|
14745
|
+
id: "DecisionStatus",
|
|
14746
|
+
description: "Decision nodes only: proposed \u2192 approved (agreed, not yet reality) \u2192 enacted (in effect); terminal: rejected, deprecated, superseded. Not a lifecycle status \u2014 the node's status field is kept in sync (proposed\u2192discovery, approved\u2192backlog, enacted\u2192live, terminals\u2192archived)."
|
|
14747
|
+
});
|
|
14628
14748
|
|
|
14629
14749
|
// ../schema/src/id-gen.ts
|
|
14630
14750
|
var SPECIES_PREFIXES = {
|
|
@@ -14632,7 +14752,8 @@ var SPECIES_PREFIXES = {
|
|
|
14632
14752
|
view: "V-",
|
|
14633
14753
|
"data-model": "DM-",
|
|
14634
14754
|
"api-endpoint": "API-",
|
|
14635
|
-
acceptance: "AC-"
|
|
14755
|
+
acceptance: "AC-",
|
|
14756
|
+
decision: "DEC-"
|
|
14636
14757
|
};
|
|
14637
14758
|
|
|
14638
14759
|
// ../schema/src/playlist.ts
|
|
@@ -14730,6 +14851,7 @@ function parseJournalLines(text) {
|
|
|
14730
14851
|
var NODE_REF_FIELDS = {
|
|
14731
14852
|
"node.updated": ["node_id"],
|
|
14732
14853
|
"node.status_changed": ["node_id"],
|
|
14854
|
+
"decision.status_changed": ["node_id"],
|
|
14733
14855
|
"node.deleted": ["node_id"],
|
|
14734
14856
|
"edge.added": ["source_id", "target_id"],
|
|
14735
14857
|
"ref.added": ["node_id"],
|
|
@@ -14738,6 +14860,12 @@ var NODE_REF_FIELDS = {
|
|
|
14738
14860
|
"idea.proposed": ["node_id"],
|
|
14739
14861
|
"request.filed": ["node_id"]
|
|
14740
14862
|
};
|
|
14863
|
+
function statusesAgree(last, snapshot) {
|
|
14864
|
+
if (last === snapshot) return true;
|
|
14865
|
+
if (typeof snapshot !== "string") return false;
|
|
14866
|
+
if (last === "backlog" && snapshot === "idea") return true;
|
|
14867
|
+
return (normalizeStatus(last) ?? last) === (normalizeStatus(snapshot) ?? snapshot);
|
|
14868
|
+
}
|
|
14741
14869
|
function crossCheckJournal(bundle) {
|
|
14742
14870
|
const findings = [];
|
|
14743
14871
|
const journalRaw = bundle.journal;
|
|
@@ -14756,9 +14884,15 @@ function crossCheckJournal(bundle) {
|
|
|
14756
14884
|
const nodesRaw = Array.isArray(bundle.nodes) ? bundle.nodes : [];
|
|
14757
14885
|
const edgesRaw = Array.isArray(bundle.edges) ? bundle.edges : [];
|
|
14758
14886
|
const snapshotNodeStatus = /* @__PURE__ */ new Map();
|
|
14887
|
+
const snapshotDecisionStatus = /* @__PURE__ */ new Map();
|
|
14759
14888
|
for (const n of nodesRaw) {
|
|
14760
14889
|
const id = str(n?.id);
|
|
14761
|
-
if (id !== void 0)
|
|
14890
|
+
if (id !== void 0) {
|
|
14891
|
+
snapshotNodeStatus.set(id, n.status);
|
|
14892
|
+
const metadata = n.metadata;
|
|
14893
|
+
const decisionStatus = metadata && typeof metadata === "object" && !Array.isArray(metadata) ? metadata.decision_status : void 0;
|
|
14894
|
+
snapshotDecisionStatus.set(id, decisionStatus ?? "proposed");
|
|
14895
|
+
}
|
|
14762
14896
|
}
|
|
14763
14897
|
const snapshotEdgeIds = /* @__PURE__ */ new Set();
|
|
14764
14898
|
for (const e of edgesRaw) {
|
|
@@ -14807,6 +14941,7 @@ function crossCheckJournal(bundle) {
|
|
|
14807
14941
|
const ordered = orderEvents(valid.map((v) => v.ev));
|
|
14808
14942
|
const created = /* @__PURE__ */ new Set();
|
|
14809
14943
|
const lastProjectStatus = /* @__PURE__ */ new Map();
|
|
14944
|
+
const lastDecisionStatus = /* @__PURE__ */ new Map();
|
|
14810
14945
|
for (const ev of ordered) {
|
|
14811
14946
|
if (ev.type === "node.created") {
|
|
14812
14947
|
const nid = str(ev.node_id);
|
|
@@ -14817,6 +14952,12 @@ function crossCheckJournal(bundle) {
|
|
|
14817
14952
|
const to = str(ev.to);
|
|
14818
14953
|
if (to !== void 0) lastProjectStatus.set(nid, to);
|
|
14819
14954
|
}
|
|
14955
|
+
} else if (ev.type === "decision.status_changed") {
|
|
14956
|
+
const nid = str(ev.node_id);
|
|
14957
|
+
if (nid) {
|
|
14958
|
+
const to = str(ev.to);
|
|
14959
|
+
if (to !== void 0) lastDecisionStatus.set(nid, to);
|
|
14960
|
+
}
|
|
14820
14961
|
}
|
|
14821
14962
|
}
|
|
14822
14963
|
for (const { ev, index } of valid) {
|
|
@@ -14834,6 +14975,19 @@ function crossCheckJournal(bundle) {
|
|
|
14834
14975
|
}
|
|
14835
14976
|
}
|
|
14836
14977
|
}
|
|
14978
|
+
if (ev.type === "deliverable.shipped" && Array.isArray(ev.node_ids)) {
|
|
14979
|
+
ev.node_ids.forEach((raw, i) => {
|
|
14980
|
+
const ref = str(raw);
|
|
14981
|
+
if (ref !== void 0 && !everNodes.has(ref)) {
|
|
14982
|
+
findings.push({
|
|
14983
|
+
path: `journal[${index}].node_ids[${i}]`,
|
|
14984
|
+
rule: "journal-dangling-node-ref",
|
|
14985
|
+
message: `journal[${index}] (${ev.type}): references node "${ref}" that never existed in the snapshot or journal.`,
|
|
14986
|
+
severity: "error"
|
|
14987
|
+
});
|
|
14988
|
+
}
|
|
14989
|
+
});
|
|
14990
|
+
}
|
|
14837
14991
|
if (ev.type === "edge.removed") {
|
|
14838
14992
|
const ref = str(ev.edge_id);
|
|
14839
14993
|
if (ref !== void 0 && !everEdges.has(ref)) {
|
|
@@ -14856,7 +15010,7 @@ function crossCheckJournal(bundle) {
|
|
|
14856
15010
|
});
|
|
14857
15011
|
}
|
|
14858
15012
|
const last = lastProjectStatus.get(nodeId);
|
|
14859
|
-
if (last !== void 0 && last
|
|
15013
|
+
if (last !== void 0 && !statusesAgree(last, status)) {
|
|
14860
15014
|
findings.push({
|
|
14861
15015
|
path: "journal",
|
|
14862
15016
|
rule: "journal-status-mismatch",
|
|
@@ -14865,6 +15019,18 @@ function crossCheckJournal(bundle) {
|
|
|
14865
15019
|
});
|
|
14866
15020
|
}
|
|
14867
15021
|
}
|
|
15022
|
+
for (const [nodeId, last] of lastDecisionStatus) {
|
|
15023
|
+
if (!snapshotDecisionStatus.has(nodeId)) continue;
|
|
15024
|
+
const current = snapshotDecisionStatus.get(nodeId) ?? "proposed";
|
|
15025
|
+
if (last !== current) {
|
|
15026
|
+
findings.push({
|
|
15027
|
+
path: "journal",
|
|
15028
|
+
rule: "journal-decision-status-mismatch",
|
|
15029
|
+
message: `Node "${nodeId}": journal's last decision.status_changed.to "${last}" disagrees with snapshot decision_status "${String(current)}".`,
|
|
15030
|
+
severity: "error"
|
|
15031
|
+
});
|
|
15032
|
+
}
|
|
15033
|
+
}
|
|
14868
15034
|
return findings;
|
|
14869
15035
|
}
|
|
14870
15036
|
|
|
@@ -14895,10 +15061,19 @@ var NodeStatusChangedEventSchema = external_exports.object({
|
|
|
14895
15061
|
...envelope,
|
|
14896
15062
|
type: external_exports.literal("node.status_changed"),
|
|
14897
15063
|
node_id: external_exports.string(),
|
|
14898
|
-
|
|
14899
|
-
|
|
15064
|
+
// History is never rewritten (docs/spec/journal.md): pre-v3 events keep
|
|
15065
|
+
// their legacy status ids, so strict per-type validation must accept them.
|
|
15066
|
+
from: AnyStatusSchema,
|
|
15067
|
+
to: AnyStatusSchema,
|
|
14900
15068
|
platform: PlatformSchema.optional()
|
|
14901
15069
|
}).catchall(external_exports.unknown());
|
|
15070
|
+
var DecisionStatusChangedEventSchema = external_exports.object({
|
|
15071
|
+
...envelope,
|
|
15072
|
+
type: external_exports.literal("decision.status_changed"),
|
|
15073
|
+
node_id: external_exports.string(),
|
|
15074
|
+
from: DecisionStatusSchema,
|
|
15075
|
+
to: DecisionStatusSchema
|
|
15076
|
+
}).catchall(external_exports.unknown());
|
|
14902
15077
|
var NodeDeletedEventSchema = external_exports.object({ ...envelope, type: external_exports.literal("node.deleted"), node_id: external_exports.string() }).catchall(external_exports.unknown());
|
|
14903
15078
|
var EdgeAddedEventSchema = external_exports.object({
|
|
14904
15079
|
...envelope,
|
|
@@ -14916,6 +15091,16 @@ var ReleaseTaggedEventSchema = external_exports.object({
|
|
|
14916
15091
|
notes: external_exports.string().optional(),
|
|
14917
15092
|
platform: PlatformSchema.optional()
|
|
14918
15093
|
}).catchall(external_exports.unknown());
|
|
15094
|
+
var DeliverableShippedEventSchema = external_exports.object({
|
|
15095
|
+
...envelope,
|
|
15096
|
+
type: external_exports.literal("deliverable.shipped"),
|
|
15097
|
+
deliverable_id: external_exports.string(),
|
|
15098
|
+
title: external_exports.string(),
|
|
15099
|
+
summary: external_exports.string().optional(),
|
|
15100
|
+
url: external_exports.string().optional(),
|
|
15101
|
+
node_ids: external_exports.array(external_exports.string()).optional(),
|
|
15102
|
+
platform: PlatformSchema.optional()
|
|
15103
|
+
}).catchall(external_exports.unknown());
|
|
14919
15104
|
var IdeaProposedEventSchema = external_exports.object({
|
|
14920
15105
|
...envelope,
|
|
14921
15106
|
type: external_exports.literal("idea.proposed"),
|
|
@@ -14953,10 +15138,12 @@ var KnownJournalEventSchema = external_exports.union([
|
|
|
14953
15138
|
NodeCreatedEventSchema,
|
|
14954
15139
|
NodeUpdatedEventSchema,
|
|
14955
15140
|
NodeStatusChangedEventSchema,
|
|
15141
|
+
DecisionStatusChangedEventSchema,
|
|
14956
15142
|
NodeDeletedEventSchema,
|
|
14957
15143
|
EdgeAddedEventSchema,
|
|
14958
15144
|
EdgeRemovedEventSchema,
|
|
14959
15145
|
ReleaseTaggedEventSchema,
|
|
15146
|
+
DeliverableShippedEventSchema,
|
|
14960
15147
|
IdeaProposedEventSchema,
|
|
14961
15148
|
RequestFiledEventSchema,
|
|
14962
15149
|
RefAddedEventSchema,
|
|
@@ -14967,7 +15154,8 @@ var KnownJournalEventSchema = external_exports.union([
|
|
|
14967
15154
|
// ../schema/src/bundle.ts
|
|
14968
15155
|
var PlatformStatusMapSchema = external_exports.partialRecord(
|
|
14969
15156
|
PlatformSchema,
|
|
14970
|
-
|
|
15157
|
+
// legacy-tolerant: migrateStatusVocabulary normalizes on load
|
|
15158
|
+
AnyStatusSchema
|
|
14971
15159
|
).meta({ id: "PlatformStatusMap", description: "Per-platform status overrides for view nodes." });
|
|
14972
15160
|
var PlatformNotesMapSchema = external_exports.partialRecord(
|
|
14973
15161
|
PlatformSchema,
|
|
@@ -14990,7 +15178,8 @@ var RefSchema = external_exports.object({
|
|
|
14990
15178
|
external_status: external_exports.string().optional().meta({
|
|
14991
15179
|
description: 'Mirrored external state, verbatim (e.g. "open", "merged", "In Progress").'
|
|
14992
15180
|
}),
|
|
14993
|
-
|
|
15181
|
+
// legacy-tolerant: migrateStatusVocabulary normalizes on load
|
|
15182
|
+
status_mapped: AnyStatusSchema.optional().meta({
|
|
14994
15183
|
description: "Optional mapping of external_status into the arkaik lifecycle. Advisory display data \u2014 never mutates node.status."
|
|
14995
15184
|
}),
|
|
14996
15185
|
platform: PlatformSchema.optional().meta({ description: "Optional scoping to one platform variant." }),
|
|
@@ -14998,6 +15187,9 @@ var RefSchema = external_exports.object({
|
|
|
14998
15187
|
}).meta({ id: "Ref", description: "A typed external reference on a node (docs/spec/bundle-format.md \xA7 References)." });
|
|
14999
15188
|
var NodeMetadataSchema = external_exports.object({
|
|
15000
15189
|
stage: external_exports.string().optional(),
|
|
15190
|
+
blocked_by: external_exports.string().optional().meta({
|
|
15191
|
+
description: "Non-empty = blocked at the current status. A node id (rendered as a link) or free text naming the dependency."
|
|
15192
|
+
}),
|
|
15001
15193
|
playlist: FlowPlaylistSchema.optional(),
|
|
15002
15194
|
platformNotes: PlatformNotesMapSchema.optional(),
|
|
15003
15195
|
platformStatuses: PlatformStatusMapSchema.optional(),
|
|
@@ -15008,6 +15200,21 @@ var NodeMetadataSchema = external_exports.object({
|
|
|
15008
15200
|
}),
|
|
15009
15201
|
values: external_exports.array(ValueSchema).optional().meta({
|
|
15010
15202
|
description: "Acceptance nodes only: 1..n Bain value elements served (the Why)."
|
|
15203
|
+
}),
|
|
15204
|
+
product: external_exports.string().optional().meta({
|
|
15205
|
+
description: "Product membership (docs/spec/bundle-format.md \xA7 Products); flow, view, and acceptance only."
|
|
15206
|
+
}),
|
|
15207
|
+
decision_status: DecisionStatusSchema.optional().meta({
|
|
15208
|
+
description: "Decision nodes only: proposed | approved | enacted | rejected | deprecated | superseded. The node's lifecycle status is kept in sync (spec \xA72)."
|
|
15209
|
+
}),
|
|
15210
|
+
context: external_exports.string().optional().meta({
|
|
15211
|
+
description: "Decision nodes only: Context \u2014 the Why (markdown)."
|
|
15212
|
+
}),
|
|
15213
|
+
consequences: external_exports.string().optional().meta({
|
|
15214
|
+
description: "Decision nodes only: Consequences \u2014 the How (markdown)."
|
|
15215
|
+
}),
|
|
15216
|
+
decided_at: external_exports.string().optional().meta({
|
|
15217
|
+
description: "Decision nodes only: ISO 8601 date the decision was made."
|
|
15011
15218
|
})
|
|
15012
15219
|
}).catchall(external_exports.unknown()).meta({ id: "NodeMetadata", description: "Optional metadata for a node." });
|
|
15013
15220
|
var NodeSchema = external_exports.object({
|
|
@@ -15016,7 +15223,8 @@ var NodeSchema = external_exports.object({
|
|
|
15016
15223
|
species: SpeciesSchema,
|
|
15017
15224
|
title: external_exports.string().meta({ description: "Human-readable node title." }),
|
|
15018
15225
|
description: external_exports.string().optional().meta({ description: "Optional description of the node's purpose." }),
|
|
15019
|
-
|
|
15226
|
+
// legacy-tolerant: migrateStatusVocabulary normalizes on load
|
|
15227
|
+
status: AnyStatusSchema,
|
|
15020
15228
|
platforms: external_exports.array(PlatformSchema).meta({ description: "One or more target platforms." }),
|
|
15021
15229
|
metadata: NodeMetadataSchema.optional()
|
|
15022
15230
|
}).meta({ id: "Node" });
|
|
@@ -15028,6 +15236,18 @@ var EdgeSchema = external_exports.object({
|
|
|
15028
15236
|
edge_type: EdgeTypeSchema,
|
|
15029
15237
|
metadata: external_exports.record(external_exports.string(), external_exports.unknown()).optional().meta({ description: "Optional edge metadata." })
|
|
15030
15238
|
}).meta({ id: "Edge" });
|
|
15239
|
+
var MapDisplayOptionsSchema = external_exports.object({
|
|
15240
|
+
images: external_exports.boolean().optional().meta({ description: "Screenshot (or cover) art on view cards." }),
|
|
15241
|
+
flow_platforms: external_exports.string().optional().meta({
|
|
15242
|
+
description: "A flow card's platform delivery: rings (default) | bars."
|
|
15243
|
+
}),
|
|
15244
|
+
view_platforms: external_exports.string().optional().meta({
|
|
15245
|
+
description: "A view card's platform availability: chips (default) | rows."
|
|
15246
|
+
}),
|
|
15247
|
+
minimap_color: external_exports.string().optional().meta({
|
|
15248
|
+
description: "What a minimap node's fill encodes: status (default) | species."
|
|
15249
|
+
})
|
|
15250
|
+
}).catchall(external_exports.unknown()).meta({ id: "MapDisplayOptions", description: "How a map draws its cards (docs/spec/maps.md \xA7 Display Options)." });
|
|
15031
15251
|
var MapDefinitionSchema = external_exports.object({
|
|
15032
15252
|
id: external_exports.string().meta({
|
|
15033
15253
|
description: "Kebab-case, unique within the project; built-in ids (journey, system) are reserved."
|
|
@@ -15046,13 +15266,32 @@ var MapDefinitionSchema = external_exports.object({
|
|
|
15046
15266
|
root_node_id: external_exports.string().optional().meta({
|
|
15047
15267
|
description: "Scope anchor: the subgraph is the undirected neighborhood reachable from this node."
|
|
15048
15268
|
}),
|
|
15269
|
+
product: external_exports.string().optional().meta({ description: "Product scope; absent = every product." }),
|
|
15049
15270
|
depth: external_exports.number().optional().meta({ description: "Traversal bound from the root; absent = unbounded." }),
|
|
15050
|
-
layout: external_exports.object({ direction: external_exports.string().optional() }).catchall(external_exports.unknown()).optional().meta({ description: "Renderer layout hints (e.g. direction: DOWN | RIGHT)." })
|
|
15271
|
+
layout: external_exports.object({ direction: external_exports.string().optional() }).catchall(external_exports.unknown()).optional().meta({ description: "Renderer layout hints (e.g. direction: DOWN | RIGHT)." }),
|
|
15272
|
+
display: MapDisplayOptionsSchema.optional().meta({
|
|
15273
|
+
description: "Card rendering; the human twin is project.metadata.map_display[id]."
|
|
15274
|
+
})
|
|
15051
15275
|
}).catchall(external_exports.unknown()).meta({ id: "MapDefinition", description: "A stored map definition (docs/spec/maps.md \xA7 MapDefinition)." });
|
|
15276
|
+
var ProductDefinitionSchema = external_exports.object({
|
|
15277
|
+
id: external_exports.string().meta({ description: "Kebab-case, unique within the project." }),
|
|
15278
|
+
title: external_exports.string().meta({ description: "Display title." }),
|
|
15279
|
+
description: external_exports.string().optional().meta({ description: "What this product is." }),
|
|
15280
|
+
platforms: external_exports.array(PlatformSchema).meta({
|
|
15281
|
+
description: "The platforms this product can ship on; empty means availability is not tracked."
|
|
15282
|
+
}),
|
|
15283
|
+
root_node_id: external_exports.string().optional().meta({ description: "This product's journey anchor." })
|
|
15284
|
+
}).catchall(external_exports.unknown()).meta({ id: "ProductDefinition", description: "A product definition (docs/spec/bundle-format.md \xA7 Products)." });
|
|
15052
15285
|
var ProjectMetadataSchema = external_exports.object({
|
|
15053
15286
|
view_card_variant: external_exports.enum(["compact", "large"]).optional(),
|
|
15054
15287
|
maps: external_exports.array(MapDefinitionSchema).optional().meta({
|
|
15055
15288
|
description: "Stored map definitions (docs/spec/maps.md \xA7 Storage) \u2014 additive; unknown fields preserved."
|
|
15289
|
+
}),
|
|
15290
|
+
map_display: external_exports.record(external_exports.string(), MapDisplayOptionsSchema).optional().meta({
|
|
15291
|
+
description: "Per-map display overrides keyed by map id (docs/spec/maps.md \xA7 Display Options) \u2014 the only path open to the built-in maps."
|
|
15292
|
+
}),
|
|
15293
|
+
products: external_exports.array(ProductDefinitionSchema).optional().meta({
|
|
15294
|
+
description: "Product definitions (docs/spec/bundle-format.md \xA7 Products) \u2014 additive; unknown fields preserved."
|
|
15056
15295
|
})
|
|
15057
15296
|
}).catchall(external_exports.unknown()).meta({ id: "ProjectMetadata", description: "Optional project-level UI settings." });
|
|
15058
15297
|
var ProjectSchema = external_exports.object({
|
|
@@ -15089,11 +15328,31 @@ var ProjectBundleSchema = external_exports.object({
|
|
|
15089
15328
|
});
|
|
15090
15329
|
|
|
15091
15330
|
// ../schema/src/maps.ts
|
|
15331
|
+
var MAP_FLOW_PLATFORMS_MODES = ["rings", "bars"];
|
|
15332
|
+
var MAP_VIEW_PLATFORMS_MODES = ["chips", "rows"];
|
|
15333
|
+
var MAP_MINIMAP_COLOR_MODES = ["status", "species"];
|
|
15092
15334
|
var BUILT_IN_MAP_IDS = ["journey", "system"];
|
|
15093
15335
|
function isBuiltInMapId(id) {
|
|
15094
15336
|
return BUILT_IN_MAP_IDS.includes(id);
|
|
15095
15337
|
}
|
|
15096
15338
|
|
|
15339
|
+
// ../schema/src/products.ts
|
|
15340
|
+
var PRODUCT_MEMBERSHIP_SPECIES = ["flow", "view", "acceptance"];
|
|
15341
|
+
function resolveProducts(project) {
|
|
15342
|
+
const stored = project?.metadata?.products;
|
|
15343
|
+
if (!Array.isArray(stored)) return [];
|
|
15344
|
+
const seen = /* @__PURE__ */ new Set();
|
|
15345
|
+
const products = [];
|
|
15346
|
+
for (const entry of stored) {
|
|
15347
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) continue;
|
|
15348
|
+
const candidate = entry;
|
|
15349
|
+
if (typeof candidate.id !== "string" || candidate.id.trim() === "" || seen.has(candidate.id)) continue;
|
|
15350
|
+
seen.add(candidate.id);
|
|
15351
|
+
products.push(candidate);
|
|
15352
|
+
}
|
|
15353
|
+
return products;
|
|
15354
|
+
}
|
|
15355
|
+
|
|
15097
15356
|
// ../schema/src/validate.ts
|
|
15098
15357
|
var VALID_STAGES = ["beta", "monitoring", "deprecated"];
|
|
15099
15358
|
var VALID_VIEW_CARD_VARIANTS = ["compact", "large"];
|
|
@@ -15105,25 +15364,6 @@ function estimateDataUriBytes(dataUri) {
|
|
|
15105
15364
|
const padding = payload.endsWith("==") ? 2 : payload.endsWith("=") ? 1 : 0;
|
|
15106
15365
|
return Math.floor(payload.length * 3 / 4) - padding;
|
|
15107
15366
|
}
|
|
15108
|
-
var VALID_EDGE_SEMANTICS = {
|
|
15109
|
-
composes: [
|
|
15110
|
-
["flow", "view"],
|
|
15111
|
-
["flow", "flow"],
|
|
15112
|
-
["view", "flow"],
|
|
15113
|
-
["view", "view"]
|
|
15114
|
-
],
|
|
15115
|
-
calls: [
|
|
15116
|
-
["view", "api-endpoint"],
|
|
15117
|
-
["flow", "api-endpoint"],
|
|
15118
|
-
["api-endpoint", "api-endpoint"]
|
|
15119
|
-
],
|
|
15120
|
-
displays: [["view", "data-model"]],
|
|
15121
|
-
queries: [["api-endpoint", "data-model"]],
|
|
15122
|
-
covers: [
|
|
15123
|
-
["acceptance", "view"],
|
|
15124
|
-
["acceptance", "flow"]
|
|
15125
|
-
]
|
|
15126
|
-
};
|
|
15127
15367
|
function isIsoDate(value) {
|
|
15128
15368
|
return typeof value === "string" && !Number.isNaN(Date.parse(value));
|
|
15129
15369
|
}
|
|
@@ -15219,7 +15459,9 @@ function validateBundle(input) {
|
|
|
15219
15459
|
}
|
|
15220
15460
|
const platforms = node.platforms;
|
|
15221
15461
|
if (!platforms || platforms.length === 0) {
|
|
15222
|
-
|
|
15462
|
+
if (species !== "decision") {
|
|
15463
|
+
error51(`${base}.platforms`, "platforms-non-empty", `Node ${nodeId}: platforms array is empty or missing`);
|
|
15464
|
+
}
|
|
15223
15465
|
} else {
|
|
15224
15466
|
for (const p of platforms) {
|
|
15225
15467
|
if (!PLATFORM_IDS.includes(p)) {
|
|
@@ -15341,6 +15583,25 @@ function validateBundle(input) {
|
|
|
15341
15583
|
}
|
|
15342
15584
|
});
|
|
15343
15585
|
}
|
|
15586
|
+
const decisionStatus = md.decision_status;
|
|
15587
|
+
if (decisionStatus !== void 0 && species !== "decision") {
|
|
15588
|
+
warn(
|
|
15589
|
+
`${base}.metadata.decision_status`,
|
|
15590
|
+
"decision-status-wrong-species",
|
|
15591
|
+
`decision_status is meaningful on decision nodes only; "${nodeId}" is a ${species}.`
|
|
15592
|
+
);
|
|
15593
|
+
}
|
|
15594
|
+
if (species === "decision") {
|
|
15595
|
+
const effective = decisionStatusOf({ metadata: node.metadata });
|
|
15596
|
+
const expected = lifecycleStatusForDecision(effective);
|
|
15597
|
+
if (node.status !== expected) {
|
|
15598
|
+
warn(
|
|
15599
|
+
`${base}.status`,
|
|
15600
|
+
"decision-lifecycle-mismatch",
|
|
15601
|
+
`Decision "${nodeId}" is ${effective}, whose lifecycle status should be "${expected}", but status is "${node.status}" (spec \xA72).`
|
|
15602
|
+
);
|
|
15603
|
+
}
|
|
15604
|
+
}
|
|
15344
15605
|
if (species === "flow") {
|
|
15345
15606
|
const playlist = md.playlist;
|
|
15346
15607
|
if (!node.metadata || !playlist || !playlist.entries) {
|
|
@@ -15358,6 +15619,38 @@ function validateBundle(input) {
|
|
|
15358
15619
|
`project.root_node_id "${rootNodeId}" does not reference an existing node`
|
|
15359
15620
|
);
|
|
15360
15621
|
}
|
|
15622
|
+
const checkMapDisplay = (display, path, subject) => {
|
|
15623
|
+
if (typeof display !== "object" || display === null || Array.isArray(display)) return;
|
|
15624
|
+
const options = display;
|
|
15625
|
+
const modes = [
|
|
15626
|
+
["flow_platforms", MAP_FLOW_PLATFORMS_MODES],
|
|
15627
|
+
["view_platforms", MAP_VIEW_PLATFORMS_MODES],
|
|
15628
|
+
["minimap_color", MAP_MINIMAP_COLOR_MODES]
|
|
15629
|
+
];
|
|
15630
|
+
for (const [key, allowed] of modes) {
|
|
15631
|
+
const value = options[key];
|
|
15632
|
+
if (value !== void 0 && (typeof value !== "string" || !allowed.includes(value))) {
|
|
15633
|
+
warn(
|
|
15634
|
+
`${path}.${key}`,
|
|
15635
|
+
"map-unknown-display",
|
|
15636
|
+
`${subject} sets ${key} to "${String(value)}"; expected one of ${allowed.join(", ")} (renderers fall back to the default)`
|
|
15637
|
+
);
|
|
15638
|
+
}
|
|
15639
|
+
}
|
|
15640
|
+
if (options.images !== void 0 && typeof options.images !== "boolean") {
|
|
15641
|
+
warn(
|
|
15642
|
+
`${path}.images`,
|
|
15643
|
+
"map-unknown-display",
|
|
15644
|
+
`${subject} sets images to a non-boolean value (renderers fall back to the default)`
|
|
15645
|
+
);
|
|
15646
|
+
}
|
|
15647
|
+
};
|
|
15648
|
+
const mapDisplayOverrides = projectMetadata?.map_display;
|
|
15649
|
+
if (typeof mapDisplayOverrides === "object" && mapDisplayOverrides !== null && !Array.isArray(mapDisplayOverrides)) {
|
|
15650
|
+
for (const [mapId, display] of Object.entries(mapDisplayOverrides)) {
|
|
15651
|
+
checkMapDisplay(display, `project.metadata.map_display.${mapId}`, `Map "${mapId}"`);
|
|
15652
|
+
}
|
|
15653
|
+
}
|
|
15361
15654
|
const storedMaps = projectMetadata?.maps;
|
|
15362
15655
|
if (Array.isArray(storedMaps)) {
|
|
15363
15656
|
const seenMapIds = /* @__PURE__ */ new Set();
|
|
@@ -15405,8 +15698,122 @@ function validateBundle(input) {
|
|
|
15405
15698
|
}
|
|
15406
15699
|
});
|
|
15407
15700
|
}
|
|
15701
|
+
checkMapDisplay(map2.display, `${path}.display`, `Map "${mapId ?? index}"`);
|
|
15408
15702
|
});
|
|
15409
15703
|
}
|
|
15704
|
+
const storedProducts = projectMetadata?.products;
|
|
15705
|
+
const declaredProductIds = /* @__PURE__ */ new Set();
|
|
15706
|
+
if (Array.isArray(storedProducts)) {
|
|
15707
|
+
const seenProductIds = /* @__PURE__ */ new Set();
|
|
15708
|
+
storedProducts.forEach((definition, index) => {
|
|
15709
|
+
if (typeof definition !== "object" || definition === null || Array.isArray(definition)) return;
|
|
15710
|
+
const product = definition;
|
|
15711
|
+
const path = `project.metadata.products[${index}]`;
|
|
15712
|
+
const productId = typeof product.id === "string" ? product.id : void 0;
|
|
15713
|
+
if (productId === void 0) return;
|
|
15714
|
+
if (seenProductIds.has(productId)) {
|
|
15715
|
+
warn(`${path}.id`, "product-duplicate-id", `Duplicate product id "${productId}" \u2014 the first wins`);
|
|
15716
|
+
} else {
|
|
15717
|
+
seenProductIds.add(productId);
|
|
15718
|
+
if (productId.trim() !== "") declaredProductIds.add(productId);
|
|
15719
|
+
}
|
|
15720
|
+
if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(productId)) {
|
|
15721
|
+
warn(`${path}.id`, "product-invalid-id", `Product id "${productId}" is not kebab-case`);
|
|
15722
|
+
}
|
|
15723
|
+
});
|
|
15724
|
+
}
|
|
15725
|
+
const hasProducts = declaredProductIds.size > 0;
|
|
15726
|
+
const anchorsByAcceptance = /* @__PURE__ */ new Map();
|
|
15727
|
+
for (const edge of edges) {
|
|
15728
|
+
if (edge.edge_type !== "covers") continue;
|
|
15729
|
+
const source = typeof edge.source_id === "string" ? edge.source_id : void 0;
|
|
15730
|
+
const target = typeof edge.target_id === "string" ? edge.target_id : void 0;
|
|
15731
|
+
if (source === void 0 || target === void 0) continue;
|
|
15732
|
+
const list = anchorsByAcceptance.get(source) ?? [];
|
|
15733
|
+
list.push(target);
|
|
15734
|
+
anchorsByAcceptance.set(source, list);
|
|
15735
|
+
}
|
|
15736
|
+
const menuByProduct = /* @__PURE__ */ new Map();
|
|
15737
|
+
if (hasProducts) {
|
|
15738
|
+
for (const definition of resolveProducts({ metadata: projectMetadata })) {
|
|
15739
|
+
menuByProduct.set(
|
|
15740
|
+
definition.id,
|
|
15741
|
+
new Set(Array.isArray(definition.platforms) ? definition.platforms : [])
|
|
15742
|
+
);
|
|
15743
|
+
}
|
|
15744
|
+
}
|
|
15745
|
+
const productByNodeId = /* @__PURE__ */ new Map();
|
|
15746
|
+
const indexByNodeId = /* @__PURE__ */ new Map();
|
|
15747
|
+
nodes.forEach((node, index) => {
|
|
15748
|
+
const nodeId = typeof node.id === "string" ? node.id : `#${index}`;
|
|
15749
|
+
const species = node.species;
|
|
15750
|
+
const base = `nodes[${index}]`;
|
|
15751
|
+
indexByNodeId.set(nodeId, index);
|
|
15752
|
+
const metadata = node.metadata ?? {};
|
|
15753
|
+
const membership = typeof metadata.product === "string" ? metadata.product : void 0;
|
|
15754
|
+
const storesMembership = species !== void 0 && PRODUCT_MEMBERSHIP_SPECIES.includes(species);
|
|
15755
|
+
if (membership !== void 0 && !storesMembership) {
|
|
15756
|
+
const detail = SPECIES_IDS.includes(species) ? `${species} membership is derived from consumers and must not be stored` : "metadata.product is only meaningful on flow, view, and acceptance nodes";
|
|
15757
|
+
warn(`${base}.metadata.product`, "product-membership-wrong-species", `Node ${nodeId}: ${detail}`);
|
|
15758
|
+
return;
|
|
15759
|
+
}
|
|
15760
|
+
if (!storesMembership) return;
|
|
15761
|
+
if (membership === void 0) {
|
|
15762
|
+
if (!hasProducts) return;
|
|
15763
|
+
if (species === "acceptance") {
|
|
15764
|
+
if (!anchorsByAcceptance.has(nodeId)) {
|
|
15765
|
+
warn(
|
|
15766
|
+
`${base}.metadata.product`,
|
|
15767
|
+
"acceptance-product-unassigned",
|
|
15768
|
+
`Acceptance ${nodeId} covers nothing and names no product \u2014 it will show only under "All products"`
|
|
15769
|
+
);
|
|
15770
|
+
}
|
|
15771
|
+
} else {
|
|
15772
|
+
warn(
|
|
15773
|
+
`${base}.metadata.product`,
|
|
15774
|
+
"unassigned-membership",
|
|
15775
|
+
`Node ${nodeId}: no product membership \u2014 it will show only under "All products"`
|
|
15776
|
+
);
|
|
15777
|
+
}
|
|
15778
|
+
return;
|
|
15779
|
+
}
|
|
15780
|
+
productByNodeId.set(nodeId, membership);
|
|
15781
|
+
if (!declaredProductIds.has(membership)) {
|
|
15782
|
+
warn(
|
|
15783
|
+
`${base}.metadata.product`,
|
|
15784
|
+
"product-unknown-reference",
|
|
15785
|
+
`Node ${nodeId}: product "${membership}" is not declared on the project`
|
|
15786
|
+
);
|
|
15787
|
+
return;
|
|
15788
|
+
}
|
|
15789
|
+
const menu = menuByProduct.get(membership);
|
|
15790
|
+
const nodePlatforms = Array.isArray(node.platforms) ? node.platforms : [];
|
|
15791
|
+
if (menu) {
|
|
15792
|
+
for (const platform of nodePlatforms) {
|
|
15793
|
+
if (typeof platform === "string" && !menu.has(platform)) {
|
|
15794
|
+
warn(
|
|
15795
|
+
`${base}.platforms`,
|
|
15796
|
+
"product-platform-not-in-menu",
|
|
15797
|
+
`Node ${nodeId}: platform "${platform}" is not in product "${membership}"'s menu`
|
|
15798
|
+
);
|
|
15799
|
+
}
|
|
15800
|
+
}
|
|
15801
|
+
}
|
|
15802
|
+
});
|
|
15803
|
+
if (hasProducts) {
|
|
15804
|
+
for (const [acceptanceId, anchors] of anchorsByAcceptance) {
|
|
15805
|
+
const acceptanceIndex = indexByNodeId.get(acceptanceId);
|
|
15806
|
+
if (acceptanceIndex === void 0) continue;
|
|
15807
|
+
const spanned = new Set(anchors.map((id) => productByNodeId.get(id)).filter((id) => Boolean(id)));
|
|
15808
|
+
if (spanned.size > 1) {
|
|
15809
|
+
warn(
|
|
15810
|
+
`nodes[${acceptanceIndex}].metadata.product`,
|
|
15811
|
+
"acceptance-covers-span-products",
|
|
15812
|
+
`Acceptance ${acceptanceId} covers anchors in ${[...spanned].sort().join(" and ")} \u2014 statuses may conflate products`
|
|
15813
|
+
);
|
|
15814
|
+
}
|
|
15815
|
+
}
|
|
15816
|
+
}
|
|
15410
15817
|
const edgeIds = /* @__PURE__ */ new Set();
|
|
15411
15818
|
const edgeSignatures = /* @__PURE__ */ new Set();
|
|
15412
15819
|
const composesSet = /* @__PURE__ */ new Set();
|
|
@@ -15563,7 +15970,36 @@ function validateBundle(input) {
|
|
|
15563
15970
|
var ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
15564
15971
|
var ENCODING_LEN = ENCODING.length;
|
|
15565
15972
|
|
|
15973
|
+
// src/lib/bundle-io.ts
|
|
15974
|
+
function readBundle(filePath) {
|
|
15975
|
+
if (!existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
|
|
15976
|
+
let parsed;
|
|
15977
|
+
try {
|
|
15978
|
+
parsed = JSON.parse(readFileSync(filePath, "utf8"));
|
|
15979
|
+
} catch (e) {
|
|
15980
|
+
throw new Error(`Cannot parse JSON \u2014 ${e.message}`);
|
|
15981
|
+
}
|
|
15982
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
15983
|
+
throw new Error("Bundle must be a JSON object.");
|
|
15984
|
+
}
|
|
15985
|
+
const record2 = parsed;
|
|
15986
|
+
if (!Array.isArray(record2.nodes)) return record2;
|
|
15987
|
+
return migrateStatusVocabulary(record2);
|
|
15988
|
+
}
|
|
15989
|
+
function nodesByIdOf(bundle) {
|
|
15990
|
+
const nodes = Array.isArray(bundle.nodes) ? bundle.nodes : [];
|
|
15991
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
15992
|
+
for (const n of nodes) {
|
|
15993
|
+
if (n !== null && typeof n === "object" && typeof n.id === "string") {
|
|
15994
|
+
map2.set(n.id, n);
|
|
15995
|
+
}
|
|
15996
|
+
}
|
|
15997
|
+
return map2;
|
|
15998
|
+
}
|
|
15999
|
+
|
|
15566
16000
|
// src/lib/journal-io.ts
|
|
16001
|
+
import { appendFileSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
16002
|
+
import { dirname, join } from "node:path";
|
|
15567
16003
|
var JOURNAL_SIDECAR = "journal.jsonl";
|
|
15568
16004
|
function journalPathFor(bundlePath) {
|
|
15569
16005
|
return join(dirname(bundlePath), JOURNAL_SIDECAR);
|