@medicine-wheel/app 0.5.2 → 0.5.4
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/ALPHA.md +59 -0
- package/README.md +39 -0
- package/app/api/narrative/beats/route.ts +10 -3
- package/app/narrative/beats/page.tsx +13 -6
- package/dist/cli/beats-register.js +199 -0
- package/dist/cli/mw.js +64 -2
- package/dist/cli/orientation.js +64 -0
- package/lib/store.ts +55 -14
- package/package.json +46 -34
package/ALPHA.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# This suite is experimental alpha
|
|
2
|
+
|
|
3
|
+
**Read this before depending on anything here.**
|
|
4
|
+
|
|
5
|
+
The Medicine Wheel Developer Suite is under active development and is published
|
|
6
|
+
in the open so the work can be examined, discussed, and improved. It is not
|
|
7
|
+
finished software. Publishing early is deliberate — but so is telling you what
|
|
8
|
+
you are installing.
|
|
9
|
+
|
|
10
|
+
## What that means concretely
|
|
11
|
+
|
|
12
|
+
- **APIs change between patch versions.** All packages move in lockstep, so a
|
|
13
|
+
bump you would normally read as "nothing changed for me" may still change
|
|
14
|
+
what you depend on. Pin exact versions.
|
|
15
|
+
- **Packages appear, and their boundaries move.** Three packages were added in
|
|
16
|
+
`0.5.3`. Where a responsibility lives is still being decided.
|
|
17
|
+
- **Some packages are intentionally small first cuts.** They express an
|
|
18
|
+
intention with a working minimum rather than a finished surface. Their
|
|
19
|
+
READMEs say so where it applies.
|
|
20
|
+
- **Storage shapes are still settling.** Records written by one version may
|
|
21
|
+
need repair by a later one. Do not treat any store here as an archive of
|
|
22
|
+
record yet.
|
|
23
|
+
- **Test coverage is real but uneven.** 210 tests pass at `0.5.3`; that is a
|
|
24
|
+
floor, not a claim of completeness.
|
|
25
|
+
|
|
26
|
+
## What it does *not* mean
|
|
27
|
+
|
|
28
|
+
It does not mean the work is careless. Defects found are fixed and written
|
|
29
|
+
down, specifications are versioned alongside the code in `rispecs/`, and
|
|
30
|
+
sessions are chronicled. The alpha label is about **stability of contract**,
|
|
31
|
+
not about standard of care.
|
|
32
|
+
|
|
33
|
+
## Cultural material
|
|
34
|
+
|
|
35
|
+
Parts of this suite encode Indigenous relational research methodology —
|
|
36
|
+
Four Directions structure, ceremony protocol, OCAP® compliance surfaces,
|
|
37
|
+
relational accountability. That material is included because the software is
|
|
38
|
+
*for* that way of working, not as decoration.
|
|
39
|
+
|
|
40
|
+
If you are building on those parts, please read them as an invitation to work
|
|
41
|
+
relationally rather than as a finished implementation of anyone's protocol.
|
|
42
|
+
Where a design question is properly a knowledge holder's decision rather than
|
|
43
|
+
an engineer's, the specs try to say so. Where we have got that wrong, telling
|
|
44
|
+
us is a contribution.
|
|
45
|
+
|
|
46
|
+
## If you are evaluating this
|
|
47
|
+
|
|
48
|
+
The most useful things to read first:
|
|
49
|
+
|
|
50
|
+
- `rispecs/` — the specifications, versioned with the code
|
|
51
|
+
- `README.md` — what the suite is
|
|
52
|
+
- Open issues — where the known edges are, stated plainly
|
|
53
|
+
|
|
54
|
+
Feedback is genuinely wanted at this stage, including "this boundary is wrong"
|
|
55
|
+
and "this should not be software".
|
|
56
|
+
|
|
57
|
+
## License
|
|
58
|
+
|
|
59
|
+
MIT — see `LICENSE`.
|
package/README.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
> A first experimental TypeScript framework for relational healing, ceremonial inquiry, and Indigenous-aligned software development — grounded in the Four Directions, Wilson's three R's (Respect, Reciprocity, Responsibility), and OCAP® data sovereignty principles.
|
|
4
4
|
|
|
5
|
+
> [!WARNING]
|
|
6
|
+
> **Experimental alpha.** APIs change between patch versions, packages appear and
|
|
7
|
+
> their boundaries move, and storage shapes are still settling. Published in the
|
|
8
|
+
> open so the work can be examined and improved — pin exact versions, and read
|
|
9
|
+
> [ALPHA.md](./ALPHA.md) before depending on any of it.
|
|
10
|
+
|
|
5
11
|
## NEWS
|
|
6
12
|
|
|
7
13
|
* Full postgres (neon) data provider !
|
|
@@ -25,8 +31,17 @@ medicine-wheel-ontology-core ← Foundation (types, schemas, RDF vocabu
|
|
|
25
31
|
├── medicine-wheel-ui-components ← React components
|
|
26
32
|
├── medicine-wheel-data-store ← Shared Redis data access
|
|
27
33
|
└── medicine-wheel-session-reader ← Session event data reader
|
|
34
|
+
|
|
35
|
+
@medicine-wheel/creative-orientation ← The question asked before the work
|
|
36
|
+
├── @medicine-wheel/creative-problem-solving ← Signpost — re-exports the above
|
|
37
|
+
├── @medicine-wheel/gap-analysis ← The fire path
|
|
38
|
+
└── @medicine-wheel/brainstorming ← Idea → design, through human gates
|
|
28
39
|
```
|
|
29
40
|
|
|
41
|
+
> **Is there a prior state you are restoring?** Named one — this is a fire, and
|
|
42
|
+
> gap analysis is the right instrument. Named none — you are creating, and
|
|
43
|
+
> structural tension is. `mw orient "<outcome>"` asks it from the command line.
|
|
44
|
+
|
|
30
45
|
## Methodology: RISE Framework
|
|
31
46
|
|
|
32
47
|
This project utilizes the **RISE** methodology:
|
|
@@ -129,6 +144,30 @@ Session event reader — JSONL parsing, session summaries, analytics extraction,
|
|
|
129
144
|
- **Version:** 0.2.0
|
|
130
145
|
- **Dependencies:** None (Node.js built-ins only)
|
|
131
146
|
|
|
147
|
+
### [@medicine-wheel/creative-orientation](src/creative-orientation)
|
|
148
|
+
The orientation question, asked before the work: *is there a prior state you are restoring?* Yes — this is a fire, route to gap analysis. No — you are creating, route to structural tension. Reads the claim the caller supplies; advises where phrasing and situation disagree, and never refuses.
|
|
149
|
+
|
|
150
|
+
- **Version:** 0.5.3
|
|
151
|
+
- **Dependencies:** None (Node.js built-ins only)
|
|
152
|
+
|
|
153
|
+
### [@medicine-wheel/creative-problem-solving](src/creative-problem-solving)
|
|
154
|
+
Signpost package — re-exports `@medicine-wheel/creative-orientation` and adds `THE_QUESTION`. Holds no logic of its own. It exists because "creative problem solving" is the name people look for, and a signpost carrying the traveller's name is how they find the door.
|
|
155
|
+
|
|
156
|
+
- **Version:** 0.5.3
|
|
157
|
+
- **Dependencies:** `@medicine-wheel/creative-orientation`
|
|
158
|
+
|
|
159
|
+
### [@medicine-wheel/gap-analysis](src/gap-analysis)
|
|
160
|
+
Problem-solving built properly for when something worked and stopped — evidenced baseline, observation, difference, verifiable elimination steps. Root cause, incidents, regressions, troubleshooting. The baseline requirement is what separates a fire from a creating act.
|
|
161
|
+
|
|
162
|
+
- **Version:** 0.5.3
|
|
163
|
+
- **Dependencies:** `@medicine-wheel/creative-orientation`
|
|
164
|
+
|
|
165
|
+
### [@medicine-wheel/brainstorming](src/brainstorming)
|
|
166
|
+
Idea into committed design through approval gates a human holds — `explore → clarify → approaches → design → spec → review → plan`. Every outcome it emits, including its own multiple-choice questions, passes the orientation question before it is spoken.
|
|
167
|
+
|
|
168
|
+
- **Version:** 0.5.3
|
|
169
|
+
- **Dependencies:** `@medicine-wheel/creative-orientation`
|
|
170
|
+
|
|
132
171
|
## Specifications
|
|
133
172
|
|
|
134
173
|
RISE framework specifications for all 15 packages are in [`rispecs/`](rispecs/). Start with [`medicine-wheel.spec.md`](rispecs/medicine-wheel.spec.md) for the system overview.
|
|
@@ -15,7 +15,7 @@ export async function POST(request: Request) {
|
|
|
15
15
|
// Pass the caller's id and timestamp through when supplied. A client that
|
|
16
16
|
// authored a beat holds its id and will look it up again — minting a fresh
|
|
17
17
|
// one here hands back a beat the caller can never find.
|
|
18
|
-
const beat = createBeat({
|
|
18
|
+
const { beat, warnings } = createBeat({
|
|
19
19
|
id: body.id,
|
|
20
20
|
timestamp: body.timestamp,
|
|
21
21
|
direction: body.direction,
|
|
@@ -31,8 +31,15 @@ export async function POST(request: Request) {
|
|
|
31
31
|
sub_beats: body.sub_beats,
|
|
32
32
|
origin: body.origin,
|
|
33
33
|
});
|
|
34
|
-
|
|
34
|
+
// Warnings ride on the created beat rather than replacing it, so clients
|
|
35
|
+
// that read the beat back by id keep working while advisory findings stop
|
|
36
|
+
// being computed-and-discarded.
|
|
37
|
+
return NextResponse.json(warnings.length ? { ...beat, warnings } : beat, { status: 201 });
|
|
35
38
|
} catch (error: any) {
|
|
36
|
-
|
|
39
|
+
// An invalid draft is the caller's error, not the server's. Reporting it
|
|
40
|
+
// as a 500 would read as "we broke" when the truth is "this beat does not
|
|
41
|
+
// satisfy the wheel's laws and was not stored".
|
|
42
|
+
const invalid = /^Invalid beat draft/.test(error?.message ?? '');
|
|
43
|
+
return NextResponse.json({ error: error.message }, { status: invalid ? 400 : 500 });
|
|
37
44
|
}
|
|
38
45
|
}
|
|
@@ -25,23 +25,32 @@ export default function BeatsPage() {
|
|
|
25
25
|
async function addBeat(e: FormEvent<HTMLFormElement>) {
|
|
26
26
|
e.preventDefault();
|
|
27
27
|
const form = new FormData(e.currentTarget);
|
|
28
|
+
// The act is never sent: it is derived from the direction by the engine.
|
|
29
|
+
// Offering it as a form field is how a west beat came to be stored in act 1.
|
|
28
30
|
const body = {
|
|
29
31
|
direction: form.get("direction") as string,
|
|
30
32
|
title: form.get("title") as string,
|
|
31
33
|
description: form.get("description") as string,
|
|
32
34
|
prose: form.get("prose") as string || undefined,
|
|
33
|
-
act: parseInt(form.get("act") as string) || 1,
|
|
34
35
|
learnings: (form.get("learnings") as string).split(",").map((s) => s.trim()).filter(Boolean),
|
|
35
36
|
ceremonies: [],
|
|
36
37
|
relations_honored: [],
|
|
37
38
|
};
|
|
38
39
|
const res = await fetch("/api/narrative/beats", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) });
|
|
39
40
|
if (res.ok) {
|
|
40
|
-
|
|
41
|
+
const created = await res.json();
|
|
42
|
+
if (Array.isArray(created?.warnings) && created.warnings.length) {
|
|
43
|
+
toast.warning(`Beat added — ${created.warnings.join(" · ")}`);
|
|
44
|
+
} else {
|
|
45
|
+
toast.success("Beat added");
|
|
46
|
+
}
|
|
41
47
|
setShowForm(false);
|
|
42
48
|
const data = await fetch("/api/narrative/beats").then((r) => r.json());
|
|
43
49
|
setBeats(Array.isArray(data) ? data : []);
|
|
44
|
-
} else {
|
|
50
|
+
} else {
|
|
51
|
+
const err = await res.json().catch(() => null);
|
|
52
|
+
toast.error(err?.error ?? "Could not save the beat — check the form and try again");
|
|
53
|
+
}
|
|
45
54
|
}
|
|
46
55
|
|
|
47
56
|
// Direction summary
|
|
@@ -87,9 +96,7 @@ export default function BeatsPage() {
|
|
|
87
96
|
</select>
|
|
88
97
|
<textarea name="description" placeholder="Description" rows={2} required className="px-3 py-2 rounded-md border bg-background text-sm sm:col-span-2" />
|
|
89
98
|
<textarea name="prose" placeholder="Prose (optional)" rows={2} className="px-3 py-2 rounded-md border bg-background text-sm" />
|
|
90
|
-
<
|
|
91
|
-
<option value="1">Act 1 (East)</option><option value="2">Act 2 (South)</option><option value="3">Act 3 (West)</option><option value="4">Act 4 (North)</option>
|
|
92
|
-
</select>
|
|
99
|
+
<p className="px-3 py-2 text-xs text-muted-foreground self-center">Act is derived from the direction — east 1, south 2, west 3, north 4.</p>
|
|
93
100
|
<input name="learnings" placeholder="Learnings (comma-separated)" className="px-3 py-2 rounded-md border bg-background text-sm sm:col-span-2" />
|
|
94
101
|
<button type="submit" className="px-4 py-2 rounded-md bg-primary text-primary-foreground text-sm hover:opacity-90">Add beat</button>
|
|
95
102
|
</form>
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.actForDirection = void 0;
|
|
37
|
+
exports.resolveBeatsFile = resolveBeatsFile;
|
|
38
|
+
exports.registerEpisodeBeats = registerEpisodeBeats;
|
|
39
|
+
exports.describeArc = describeArc;
|
|
40
|
+
/**
|
|
41
|
+
* mw beat register — an episode's authored beats into the wheel.
|
|
42
|
+
*
|
|
43
|
+
* This is the path that was missing. `mkepisode --register` registers the
|
|
44
|
+
* episode; nothing registered the beats an episode carries, so they sat in a
|
|
45
|
+
* YAML file as a design artifact and never became records.
|
|
46
|
+
*
|
|
47
|
+
* It goes **through** `@medicine-wheel/narrative-engine` rather than posting
|
|
48
|
+
* JSON at the REST surface. That distinction is the whole point: the first
|
|
49
|
+
* time these beats were registered it was by a script that POSTed directly,
|
|
50
|
+
* which meant `validateBeatDraft` never ran and nothing confirmed they were
|
|
51
|
+
* legal. Registering by hand around the door is the same defect the door
|
|
52
|
+
* exists to prevent.
|
|
53
|
+
*
|
|
54
|
+
* Reads `<episode>/beats/beats.yaml`:
|
|
55
|
+
*
|
|
56
|
+
* cycle_id: cycle-... # null means unbound; the cycle is created if named
|
|
57
|
+
* cycle_question: >- # used only when the cycle must be created
|
|
58
|
+
* beats:
|
|
59
|
+
* - id, direction, title, description, prose?, learnings[], relations_honored[], origin
|
|
60
|
+
*/
|
|
61
|
+
const fs = __importStar(require("fs"));
|
|
62
|
+
const path = __importStar(require("path"));
|
|
63
|
+
const narrative_engine_1 = require("@medicine-wheel/narrative-engine");
|
|
64
|
+
Object.defineProperty(exports, "actForDirection", { enumerable: true, get: function () { return narrative_engine_1.actForDirection; } });
|
|
65
|
+
/** Minimal YAML reader for the beats file — avoids a dependency for one shape. */
|
|
66
|
+
function readBeatsFile(file) {
|
|
67
|
+
// js-yaml ships with the app; require lazily so the CLI stays usable without it.
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
69
|
+
const yaml = require('js-yaml');
|
|
70
|
+
const parsed = yaml.load(fs.readFileSync(file, 'utf8'));
|
|
71
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
72
|
+
throw new Error(`${file} did not parse to an object`);
|
|
73
|
+
}
|
|
74
|
+
const { cycle_id = null, cycle_question, beats } = parsed;
|
|
75
|
+
if (!Array.isArray(beats))
|
|
76
|
+
throw new Error(`${file} has no 'beats' array`);
|
|
77
|
+
return { cycle_id, cycle_question, beats };
|
|
78
|
+
}
|
|
79
|
+
function resolveBeatsFile(episodeDir) {
|
|
80
|
+
const candidates = [
|
|
81
|
+
path.join(episodeDir, 'beats', 'beats.yaml'),
|
|
82
|
+
path.join(episodeDir, 'beats.yaml'),
|
|
83
|
+
];
|
|
84
|
+
const found = candidates.find(c => fs.existsSync(c));
|
|
85
|
+
if (!found) {
|
|
86
|
+
throw new Error(`No beats file found. Looked for:\n ${candidates.join('\n ')}`);
|
|
87
|
+
}
|
|
88
|
+
return found;
|
|
89
|
+
}
|
|
90
|
+
async function get(url) {
|
|
91
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(15000) });
|
|
92
|
+
if (!res.ok)
|
|
93
|
+
throw new Error(`HTTP ${res.status} GET ${url}`);
|
|
94
|
+
return res.json();
|
|
95
|
+
}
|
|
96
|
+
async function post(url, body) {
|
|
97
|
+
const res = await fetch(url, {
|
|
98
|
+
method: 'POST',
|
|
99
|
+
headers: { 'Content-Type': 'application/json' },
|
|
100
|
+
body: JSON.stringify(body),
|
|
101
|
+
signal: AbortSignal.timeout(20000),
|
|
102
|
+
});
|
|
103
|
+
const text = await res.text();
|
|
104
|
+
if (!res.ok) {
|
|
105
|
+
// A 400 is the wheel refusing an illegal beat. Surface its reason rather
|
|
106
|
+
// than a status code — the caller needs to know which law it broke.
|
|
107
|
+
let detail = text;
|
|
108
|
+
try {
|
|
109
|
+
detail = JSON.parse(text).error ?? text;
|
|
110
|
+
}
|
|
111
|
+
catch { /* keep raw */ }
|
|
112
|
+
throw new Error(`HTTP ${res.status}: ${detail}`);
|
|
113
|
+
}
|
|
114
|
+
return JSON.parse(text);
|
|
115
|
+
}
|
|
116
|
+
async function registerEpisodeBeats(episodeDir, options) {
|
|
117
|
+
const file = resolveBeatsFile(episodeDir);
|
|
118
|
+
const doc = readBeatsFile(file);
|
|
119
|
+
const outcome = {
|
|
120
|
+
registered: [], skipped: [], rejected: [],
|
|
121
|
+
cycleId: doc.cycle_id, cycleCreated: false,
|
|
122
|
+
};
|
|
123
|
+
// Build every draft through the door first. Nothing is written until all of
|
|
124
|
+
// them are legal — a half-registered arc is worse than none, because the
|
|
125
|
+
// cycle then reports a story that stops mid-sentence.
|
|
126
|
+
const drafts = [];
|
|
127
|
+
for (const raw of doc.beats) {
|
|
128
|
+
const draft = {
|
|
129
|
+
id: raw.id,
|
|
130
|
+
direction: raw.direction,
|
|
131
|
+
title: (raw.title ?? '').trim(),
|
|
132
|
+
description: (raw.description ?? '').trim(),
|
|
133
|
+
prose: raw.prose ? String(raw.prose).trim() : undefined,
|
|
134
|
+
learnings: raw.learnings ?? [],
|
|
135
|
+
ceremonies: raw.ceremonies ?? [],
|
|
136
|
+
relations_honored: raw.relations_honored ?? [],
|
|
137
|
+
cycle_id: doc.cycle_id ?? undefined,
|
|
138
|
+
parent_beat_id: raw.parent_beat_id,
|
|
139
|
+
origin: raw.origin ?? {
|
|
140
|
+
producer: 'chronicle-episode',
|
|
141
|
+
source_ref: path.basename(episodeDir),
|
|
142
|
+
method: 'authored',
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
const check = (0, narrative_engine_1.validateBeatDraft)(draft);
|
|
146
|
+
if (!check.valid) {
|
|
147
|
+
outcome.rejected.push({
|
|
148
|
+
id: raw.id ?? '(no id)',
|
|
149
|
+
violations: check.violations.filter(v => v.severity === 'error').map(v => `${v.field}: ${v.message}`),
|
|
150
|
+
});
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
// Prove it can be authored before anything is sent.
|
|
154
|
+
(0, narrative_engine_1.createBeat)(draft);
|
|
155
|
+
drafts.push(draft);
|
|
156
|
+
}
|
|
157
|
+
if (outcome.rejected.length > 0 || options.dryRun)
|
|
158
|
+
return outcome;
|
|
159
|
+
// Ensure the cycle exists before binding to it, so no beat is written naming
|
|
160
|
+
// a cycle that is not there.
|
|
161
|
+
if (doc.cycle_id) {
|
|
162
|
+
const cycles = await get(`${options.apiUrl}/api/narrative/cycles`);
|
|
163
|
+
if (!cycles.some(c => c.id === doc.cycle_id)) {
|
|
164
|
+
await post(`${options.apiUrl}/api/narrative/cycles`, {
|
|
165
|
+
id: doc.cycle_id,
|
|
166
|
+
research_question: doc.cycle_question ?? `Episode ${path.basename(episodeDir)}`,
|
|
167
|
+
current_direction: 'east',
|
|
168
|
+
});
|
|
169
|
+
outcome.cycleCreated = true;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const existing = await get(`${options.apiUrl}/api/narrative/beats`);
|
|
173
|
+
const known = new Set(existing.map(b => b.id));
|
|
174
|
+
for (const draft of drafts) {
|
|
175
|
+
if (draft.id && known.has(draft.id)) {
|
|
176
|
+
outcome.skipped.push({ id: draft.id, reason: 'already registered' });
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const got = await post(`${options.apiUrl}/api/narrative/beats`, draft);
|
|
180
|
+
if (got.id !== draft.id) {
|
|
181
|
+
throw new Error(`The wheel returned id ${got.id} for a beat sent as ${draft.id}. ` +
|
|
182
|
+
'Registration stopped — an id that does not survive the round trip cannot be looked up again.');
|
|
183
|
+
}
|
|
184
|
+
outcome.registered.push(got.id);
|
|
185
|
+
}
|
|
186
|
+
return outcome;
|
|
187
|
+
}
|
|
188
|
+
/** Human-readable summary of what an arc now holds. */
|
|
189
|
+
function describeArc(beats) {
|
|
190
|
+
const order = { east: 1, south: 2, west: 3, north: 4 };
|
|
191
|
+
const lines = beats
|
|
192
|
+
.slice()
|
|
193
|
+
.sort((a, b) => (a.act - b.act) || String(a.timestamp).localeCompare(String(b.timestamp)))
|
|
194
|
+
.map(b => ` act ${b.act} ${String(b.direction).padEnd(6)} ${b.title}`);
|
|
195
|
+
const walked = [...new Set(beats.map(b => b.direction))].sort((a, b) => order[a] - order[b]);
|
|
196
|
+
lines.push('');
|
|
197
|
+
lines.push(` directions walked: ${walked.join(' → ')}${walked.length === 4 ? ' (complete arc)' : ''}`);
|
|
198
|
+
return lines;
|
|
199
|
+
}
|
package/dist/cli/mw.js
CHANGED
|
@@ -46,6 +46,8 @@ const child_process_1 = require("child_process");
|
|
|
46
46
|
const path = __importStar(require("path"));
|
|
47
47
|
const fs = __importStar(require("fs"));
|
|
48
48
|
const skills_1 = require("./skills");
|
|
49
|
+
const beats_register_1 = require("./beats-register");
|
|
50
|
+
const orientation_1 = require("./orientation");
|
|
49
51
|
// ── Config ────────────────────────────────────────────────────────
|
|
50
52
|
const MW_API_URL = process.env.MW_API_URL ?? 'http://localhost:8040';
|
|
51
53
|
const MW_FORMAT = process.env.MW_FORMAT ?? 'pretty';
|
|
@@ -212,6 +214,20 @@ function cmdHelp() {
|
|
|
212
214
|
console.log(`
|
|
213
215
|
${C.bold}🌿 mw — Medicine Wheel CLI${C.reset}
|
|
214
216
|
|
|
217
|
+
ORIENTATION
|
|
218
|
+
mw orient "<outcome>" Which orientation does this call for?
|
|
219
|
+
[--restores "<prior state>"] A prior state you are returning to
|
|
220
|
+
[--evidence "<when it existed>"] When that state was actually true
|
|
221
|
+
|
|
222
|
+
Ask before problem-solving, debugging, fixing, troubleshooting, root-cause
|
|
223
|
+
or creative-problem-solving work. It reads the claim you supply:
|
|
224
|
+
|
|
225
|
+
--restores named → a fire; gap analysis is the right instrument
|
|
226
|
+
--restores absent → you are creating; structural tension is
|
|
227
|
+
|
|
228
|
+
Urgency is not evidence — under pressure every situation feels like a fire.
|
|
229
|
+
It reports and offers a restatement; it never refuses.
|
|
230
|
+
|
|
215
231
|
CEREMONY LIFECYCLE
|
|
216
232
|
mw ceremony open <intention> Open ceremony (starts in East)
|
|
217
233
|
mw ceremony close <id> [summary] Close ceremony
|
|
@@ -235,6 +251,8 @@ ${C.bold}🌿 mw — Medicine Wheel CLI${C.reset}
|
|
|
235
251
|
|
|
236
252
|
NARRATIVE
|
|
237
253
|
mw beat create <dir> <title> <desc> Create narrative beat
|
|
254
|
+
mw beat register <episode-dir> Register authored beats via the door
|
|
255
|
+
mw beat register <episode-dir> --dry-run Check legality, write nothing
|
|
238
256
|
mw beat list [--direction east] List beats
|
|
239
257
|
mw arc <cycle_id> Get narrative arc
|
|
240
258
|
|
|
@@ -525,9 +543,49 @@ async function cmdNode(positional, flags) {
|
|
|
525
543
|
}
|
|
526
544
|
}
|
|
527
545
|
// ── Beat ──────────────────────────────────────────────────────────
|
|
528
|
-
async function cmdBeat(positional) {
|
|
546
|
+
async function cmdBeat(positional, flags = {}) {
|
|
529
547
|
const sub = positional[0] ?? 'list';
|
|
530
548
|
switch (sub) {
|
|
549
|
+
case 'register': {
|
|
550
|
+
const dir = positional[1];
|
|
551
|
+
if (!dir) {
|
|
552
|
+
console.error('Usage: mw beat register <episode-dir> [--dry-run]');
|
|
553
|
+
console.error(' Registers an episode\'s authored beats through the authoring door.');
|
|
554
|
+
process.exit(2);
|
|
555
|
+
}
|
|
556
|
+
try {
|
|
557
|
+
const out = await (0, beats_register_1.registerEpisodeBeats)(dir, {
|
|
558
|
+
apiUrl: MW_API_URL,
|
|
559
|
+
dryRun: flags['dry-run'] === true,
|
|
560
|
+
});
|
|
561
|
+
if (out.rejected.length > 0) {
|
|
562
|
+
console.error(`${C.south}${out.rejected.length} beat(s) rejected — nothing was written${C.reset}`);
|
|
563
|
+
for (const r of out.rejected) {
|
|
564
|
+
console.error(` ${r.id}`);
|
|
565
|
+
for (const v of r.violations)
|
|
566
|
+
console.error(` ${v}`);
|
|
567
|
+
}
|
|
568
|
+
process.exit(1);
|
|
569
|
+
}
|
|
570
|
+
if (flags['dry-run'] === true) {
|
|
571
|
+
console.log(`${C.green}dry run — all beats are legal, nothing written${C.reset}`);
|
|
572
|
+
break;
|
|
573
|
+
}
|
|
574
|
+
if (out.cycleCreated)
|
|
575
|
+
console.log(`${C.dim}cycle created:${C.reset} ${out.cycleId}`);
|
|
576
|
+
console.log(`${C.green}registered ${out.registered.length}${C.reset}${out.skipped.length ? ` ${C.dim}(${out.skipped.length} already present)${C.reset}` : ''}`);
|
|
577
|
+
const all = (await api('GET', '/api/narrative/beats'));
|
|
578
|
+
const scoped = out.cycleId ? all.filter(b => b.cycle_id === out.cycleId) : all;
|
|
579
|
+
console.log('');
|
|
580
|
+
for (const line of (0, beats_register_1.describeArc)(scoped))
|
|
581
|
+
console.log(line);
|
|
582
|
+
}
|
|
583
|
+
catch (err) {
|
|
584
|
+
console.error(`${C.south}${err.message}${C.reset}`);
|
|
585
|
+
process.exit(1);
|
|
586
|
+
}
|
|
587
|
+
break;
|
|
588
|
+
}
|
|
531
589
|
case 'create':
|
|
532
590
|
mcpCall('create_narrative_beat', {
|
|
533
591
|
direction: positional[1] ?? '',
|
|
@@ -714,7 +772,7 @@ async function main() {
|
|
|
714
772
|
break;
|
|
715
773
|
case 'beat':
|
|
716
774
|
case 'b':
|
|
717
|
-
await cmdBeat(rest);
|
|
775
|
+
await cmdBeat(rest, flags);
|
|
718
776
|
break;
|
|
719
777
|
case 'edge':
|
|
720
778
|
case 'e':
|
|
@@ -746,6 +804,10 @@ async function main() {
|
|
|
746
804
|
case 'arc':
|
|
747
805
|
mcpCall('get_narrative_arc', { cycle_id: rest[0] ?? '' });
|
|
748
806
|
break;
|
|
807
|
+
case 'orient':
|
|
808
|
+
case 'orientation':
|
|
809
|
+
(0, orientation_1.cmdOrient)(rest, flags);
|
|
810
|
+
break;
|
|
749
811
|
default:
|
|
750
812
|
console.error(`${C.south}Unknown command: ${cmd}${C.reset}`);
|
|
751
813
|
console.error("Run 'mw help' for usage.");
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cmdOrient = cmdOrient;
|
|
4
|
+
/**
|
|
5
|
+
* mw orient — which orientation does this situation call for?
|
|
6
|
+
*
|
|
7
|
+
* The operator surface for `@medicine-wheel/creative-orientation`. The package
|
|
8
|
+
* shipped at 0.5.3 with no way to run it, which made it a library nobody could
|
|
9
|
+
* reach — a spec with a package.json.
|
|
10
|
+
*
|
|
11
|
+
* mw orient "catch cross-service contract breaks"
|
|
12
|
+
* mw orient "the image matches the packages" --restores "true through 0.5.0" --evidence "tagged 2026-07-17"
|
|
13
|
+
*
|
|
14
|
+
* It never refuses. It reports, and where the phrasing and the situation
|
|
15
|
+
* disagree it says so and offers a restatement.
|
|
16
|
+
*/
|
|
17
|
+
const creative_orientation_1 = require("@medicine-wheel/creative-orientation");
|
|
18
|
+
const C = {
|
|
19
|
+
east: '\x1b[33m', south: '\x1b[31m', west: '\x1b[34m',
|
|
20
|
+
green: '\x1b[32m', dim: '\x1b[2m', bold: '\x1b[1m', reset: '\x1b[0m',
|
|
21
|
+
};
|
|
22
|
+
function cmdOrient(positional, flags) {
|
|
23
|
+
const outcome = positional.join(' ').trim();
|
|
24
|
+
if (!outcome) {
|
|
25
|
+
console.error('Usage: mw orient "<the outcome you want>" [--restores "<prior state>"] [--evidence "<when it existed>"]');
|
|
26
|
+
console.error('');
|
|
27
|
+
console.error(' Names which orientation the situation calls for.');
|
|
28
|
+
console.error(' --restores a prior state you are returning to. Its presence is what');
|
|
29
|
+
console.error(' separates a repair from a creating act.');
|
|
30
|
+
process.exit(2);
|
|
31
|
+
}
|
|
32
|
+
const restores = typeof flags.restores === 'string' ? flags.restores : null;
|
|
33
|
+
const evidence = typeof flags.evidence === 'string' ? flags.evidence : undefined;
|
|
34
|
+
const claim = { outcome, restores, evidence };
|
|
35
|
+
const reading = (0, creative_orientation_1.readOrientation)(claim);
|
|
36
|
+
const tint = reading.orientation === 'advancing' ? C.east : C.west;
|
|
37
|
+
console.log('');
|
|
38
|
+
console.log(`${C.bold}${tint}${reading.orientation}${C.reset} → route: ${C.bold}${reading.route}${C.reset}`);
|
|
39
|
+
console.log('');
|
|
40
|
+
console.log(`${C.dim}outcome:${C.reset} ${outcome}`);
|
|
41
|
+
if (restores)
|
|
42
|
+
console.log(`${C.dim}restores:${C.reset} ${restores}${evidence ? ` ${C.dim}(${evidence})${C.reset}` : ''}`);
|
|
43
|
+
console.log('');
|
|
44
|
+
console.log(`${C.dim}signals${C.reset}`);
|
|
45
|
+
for (const s of reading.signals) {
|
|
46
|
+
const mark = s.suggests === 'advancing' ? `${C.east}↑${C.reset}` : `${C.west}↻${C.reset}`;
|
|
47
|
+
console.log(` ${mark} ${C.dim}[${s.kind}]${C.reset} ${s.detail}`);
|
|
48
|
+
}
|
|
49
|
+
if ((0, creative_orientation_1.worthSaying)(reading)) {
|
|
50
|
+
console.log('');
|
|
51
|
+
console.log(`${C.south}${C.bold}advice${C.reset}`);
|
|
52
|
+
console.log(` ${reading.advice}`);
|
|
53
|
+
if (reading.suggestedOutcome) {
|
|
54
|
+
console.log('');
|
|
55
|
+
console.log(`${C.green}suggested restatement${C.reset}`);
|
|
56
|
+
console.log(` ${reading.suggestedOutcome}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
console.log('');
|
|
61
|
+
console.log(`${C.green}the phrasing and the situation agree — nothing to say${C.reset}`);
|
|
62
|
+
}
|
|
63
|
+
console.log('');
|
|
64
|
+
}
|
package/lib/store.ts
CHANGED
|
@@ -22,7 +22,7 @@ import type {
|
|
|
22
22
|
MedicineWheelCycle,
|
|
23
23
|
} from '@/lib/types';
|
|
24
24
|
|
|
25
|
-
import {
|
|
25
|
+
import { createBeat as authorBeat, validateBeatDraft, type BeatDraft } from '@medicine-wheel/narrative-engine';
|
|
26
26
|
import { extractBeats } from './beat-response';
|
|
27
27
|
import { extractCycles, normalizeMedicineWheelCycle } from './cycle-response';
|
|
28
28
|
import { getJsonlStore } from './jsonl-store';
|
|
@@ -138,32 +138,73 @@ export function getBeatsByDirection(direction: string): NarrativeBeat[] {
|
|
|
138
138
|
// `act` is optional because it is derived from `direction`, not supplied.
|
|
139
139
|
// Requiring it invited callers to pass a constant, which is how a west beat
|
|
140
140
|
// came to be recorded in act 1.
|
|
141
|
+
/**
|
|
142
|
+
* Create a beat through the authoring door.
|
|
143
|
+
*
|
|
144
|
+
* This delegates to `@medicine-wheel/narrative-engine` rather than assembling
|
|
145
|
+
* the record itself. It did assemble it, and that made the REST surface a
|
|
146
|
+
* second minting site: a beat posted over HTTP was never checked by
|
|
147
|
+
* `validateBeatDraft`, so the door only governed callers who happened to come
|
|
148
|
+
* through MCP. Five beats were registered that way before this was noticed —
|
|
149
|
+
* they were valid, but nothing had confirmed it.
|
|
150
|
+
*
|
|
151
|
+
* Throws on an invalid draft. The route turns that into a 400, which is the
|
|
152
|
+
* honest answer: a beat that fails the wheel's laws should not be stored and
|
|
153
|
+
* then reported as created.
|
|
154
|
+
*/
|
|
141
155
|
export function createBeat(
|
|
142
156
|
data: Omit<NarrativeBeat, 'id' | 'timestamp' | 'act'> & { id?: string; timestamp?: string; act?: number },
|
|
143
|
-
): NarrativeBeat {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
157
|
+
): { beat: NarrativeBeat; warnings: string[] } {
|
|
158
|
+
// A POST carrying an existing id is an update. Its provenance was stamped at
|
|
159
|
+
// creation; re-stamping `origin` on every touch would rewrite who authored
|
|
160
|
+
// the beat — the record must keep its first producer unless the caller
|
|
161
|
+
// states a new one deliberately.
|
|
162
|
+
const existing = data.id ? (store.getBeat(data.id) as any) : undefined;
|
|
163
|
+
|
|
164
|
+
const draft: BeatDraft = {
|
|
147
165
|
direction: data.direction,
|
|
148
166
|
title: data.title,
|
|
149
167
|
description: data.description,
|
|
150
168
|
prose: data.prose,
|
|
151
169
|
ceremonies: data.ceremonies ?? [],
|
|
152
170
|
learnings: data.learnings ?? [],
|
|
153
|
-
timestamp: data.timestamp || new Date().toISOString(),
|
|
154
|
-
// Derive the act from the direction rather than defaulting to 1. A west
|
|
155
|
-
// beat posted without an act was being recorded as an opening moment,
|
|
156
|
-
// which places it wrongly on the wheel for every reader downstream.
|
|
157
|
-
act: data.act ?? actForDirection(data.direction),
|
|
158
171
|
relations_honored: data.relations_honored ?? [],
|
|
172
|
+
act: data.act,
|
|
173
|
+
id: data.id ?? crypto.randomUUID(),
|
|
174
|
+
timestamp: data.timestamp,
|
|
175
|
+
cycle_id: data.cycle_id,
|
|
176
|
+
parent_beat_id: data.parent_beat_id,
|
|
177
|
+
origin: data.origin ?? existing?.origin ?? { producer: 'rest' },
|
|
159
178
|
};
|
|
160
|
-
|
|
161
|
-
|
|
179
|
+
|
|
180
|
+
// `strictAct` turns an act that contradicts the direction into an error
|
|
181
|
+
// instead of a stored contradiction. The act is derived, never chosen; a
|
|
182
|
+
// caller supplying the natural act is fine, a caller supplying any other is
|
|
183
|
+
// told so rather than silently recorded.
|
|
184
|
+
const beat = authorBeat(draft, { strictAct: true });
|
|
185
|
+
|
|
186
|
+
// The door computes warnings on every draft; discarding them made the
|
|
187
|
+
// advisory half of validation invisible on the paths people actually use.
|
|
188
|
+
const warnings = validateBeatDraft(draft)
|
|
189
|
+
.violations.filter(v => v.severity === 'warning')
|
|
190
|
+
.map(v => `${v.field}: ${v.message}`);
|
|
191
|
+
|
|
162
192
|
if (data.sub_beats !== undefined) beat.sub_beats = data.sub_beats;
|
|
163
|
-
if (data.origin !== undefined) beat.origin = data.origin;
|
|
164
193
|
|
|
165
194
|
store.createBeat(beat as any);
|
|
166
195
|
|
|
196
|
+
// Reciprocate the child side of any sub_beats named on this beat. A parent
|
|
197
|
+
// listing children that do not point back is the same one-sided relation the
|
|
198
|
+
// cycle and parent bindings below exist to prevent.
|
|
199
|
+
if (beat.sub_beats?.length) {
|
|
200
|
+
for (const childId of beat.sub_beats) {
|
|
201
|
+
const child = store.getBeat(childId) as any;
|
|
202
|
+
if (child && child.parent_beat_id !== beat.id) {
|
|
203
|
+
store.createBeat({ ...child, parent_beat_id: beat.id });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
167
208
|
// Bind the cycle side of the relation. A beat naming a cycle that does not
|
|
168
209
|
// list it back is invisible to every arc reader that starts from the cycle.
|
|
169
210
|
if (beat.cycle_id) {
|
|
@@ -181,7 +222,7 @@ export function createBeat(
|
|
|
181
222
|
}
|
|
182
223
|
}
|
|
183
224
|
|
|
184
|
-
return beat;
|
|
225
|
+
return { beat, warnings };
|
|
185
226
|
}
|
|
186
227
|
|
|
187
228
|
// ── Cycles ──
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@medicine-wheel/app",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "Medicine Wheel — Interactive visual layer for Indigenous relational research with Four Directions, ceremonies, and narrative arcs",
|
|
5
5
|
"bin": {
|
|
6
6
|
"mw": "dist/cli/mw.js",
|
|
@@ -20,34 +20,40 @@
|
|
|
20
20
|
"Dockerfile",
|
|
21
21
|
"docker-entrypoint.sh",
|
|
22
22
|
"docker-build-push.sh",
|
|
23
|
-
"README.md"
|
|
23
|
+
"README.md",
|
|
24
|
+
"ALPHA.md"
|
|
24
25
|
],
|
|
26
|
+
"//workspaces": "TOPOLOGICAL ORDER — do not alphabetize. `npm run <script> --workspaces` executes in THIS array's order, not in dependency order: a package listed before a package it imports builds first and fails TS2307 on any tree without a stale dist/. Roots first (ontology-core, creative-orientation, session-reader), then their dependents, mcp last. A new package goes after everything it imports.",
|
|
25
27
|
"workspaces": [
|
|
28
|
+
"src/ontology-core",
|
|
29
|
+
"src/creative-orientation",
|
|
30
|
+
"src/session-reader",
|
|
26
31
|
"src/ceremony-protocol",
|
|
27
|
-
"src/
|
|
28
|
-
"src/consent-lifecycle",
|
|
32
|
+
"src/storage-provider",
|
|
29
33
|
"src/data-store",
|
|
30
34
|
"src/data-store-postgres",
|
|
31
|
-
"src/fire-keeper",
|
|
32
35
|
"src/graph-viz",
|
|
33
36
|
"src/importance-unit",
|
|
34
|
-
"src/narrative-cluster",
|
|
35
37
|
"src/narrative-engine",
|
|
36
|
-
"src/
|
|
38
|
+
"src/narrative-cluster",
|
|
37
39
|
"src/perception-layer",
|
|
38
40
|
"src/prompt-decomposition",
|
|
39
41
|
"src/relational-index",
|
|
40
42
|
"src/relational-query",
|
|
41
|
-
"src/
|
|
42
|
-
"src/
|
|
43
|
+
"src/ui-components",
|
|
44
|
+
"src/brainstorming",
|
|
45
|
+
"src/gap-analysis",
|
|
46
|
+
"src/creative-problem-solving",
|
|
47
|
+
"src/community-review",
|
|
48
|
+
"src/consent-lifecycle",
|
|
49
|
+
"src/fire-keeper",
|
|
50
|
+
"src/transformation-tracker",
|
|
43
51
|
"src/ceremonial-diary",
|
|
44
52
|
"src/github-ceremony",
|
|
45
|
-
"src/transformation-tracker",
|
|
46
|
-
"src/ui-components",
|
|
47
53
|
"mcp"
|
|
48
54
|
],
|
|
49
55
|
"scripts": {
|
|
50
|
-
"build:packages": "npm run build -w src/ontology-core && npm run build --workspaces --if-present",
|
|
56
|
+
"build:packages": "npm run build -w src/ontology-core -w src/creative-orientation && npm run build --workspaces --if-present",
|
|
51
57
|
"build:cli": "tsc -p tsconfig.cli.json && node -e \"for (const f of ['dist/cli/mw.js','dist/cli/mwsrv.js']) { try { require('fs').chmodSync(f, 0o755); } catch (e) { console.warn('[cli] chmod skipped (non-fatal):', e.message); } }\"",
|
|
52
58
|
"clean:packages": "npm run clean --workspaces --if-present",
|
|
53
59
|
"prebuild": "npm run build:packages",
|
|
@@ -78,31 +84,32 @@
|
|
|
78
84
|
"release:major": "npm run version:major && npm run publish:all && npm run release:commit"
|
|
79
85
|
},
|
|
80
86
|
"dependencies": {
|
|
81
|
-
"@medicine-wheel/ceremonial-diary": "^0.5.
|
|
82
|
-
"@medicine-wheel/ceremony-protocol": "^0.5.
|
|
83
|
-
"@medicine-wheel/community-review": "^0.5.
|
|
84
|
-
"@medicine-wheel/consent-lifecycle": "^0.5.
|
|
85
|
-
"@medicine-wheel/data-store": "^0.5.
|
|
86
|
-
"@medicine-wheel/data-store-postgres": "^0.5.
|
|
87
|
-
"@medicine-wheel/fire-keeper": "^0.5.
|
|
88
|
-
"@medicine-wheel/github-ceremony": "^0.5.
|
|
89
|
-
"@medicine-wheel/graph-viz": "^0.5.
|
|
90
|
-
"@medicine-wheel/importance-unit": "^0.5.
|
|
91
|
-
"@medicine-wheel/mcp": "^4.5.
|
|
92
|
-
"@medicine-wheel/narrative-cluster": "^0.5.
|
|
93
|
-
"@medicine-wheel/narrative-engine": "^0.5.
|
|
94
|
-
"@medicine-wheel/ontology-core": "^0.5.
|
|
95
|
-
"@medicine-wheel/perception-layer": "^0.5.
|
|
96
|
-
"@medicine-wheel/prompt-decomposition": "^0.5.
|
|
97
|
-
"@medicine-wheel/relational-index": "^0.5.
|
|
98
|
-
"@medicine-wheel/relational-query": "^0.5.
|
|
99
|
-
"@medicine-wheel/session-reader": "^0.5.
|
|
100
|
-
"@medicine-wheel/storage-provider": "^0.5.
|
|
101
|
-
"@medicine-wheel/transformation-tracker": "^0.5.
|
|
102
|
-
"@medicine-wheel/ui-components": "^0.5.
|
|
87
|
+
"@medicine-wheel/ceremonial-diary": "^0.5.4",
|
|
88
|
+
"@medicine-wheel/ceremony-protocol": "^0.5.4",
|
|
89
|
+
"@medicine-wheel/community-review": "^0.5.4",
|
|
90
|
+
"@medicine-wheel/consent-lifecycle": "^0.5.4",
|
|
91
|
+
"@medicine-wheel/data-store": "^0.5.4",
|
|
92
|
+
"@medicine-wheel/data-store-postgres": "^0.5.4",
|
|
93
|
+
"@medicine-wheel/fire-keeper": "^0.5.4",
|
|
94
|
+
"@medicine-wheel/github-ceremony": "^0.5.4",
|
|
95
|
+
"@medicine-wheel/graph-viz": "^0.5.4",
|
|
96
|
+
"@medicine-wheel/importance-unit": "^0.5.4",
|
|
97
|
+
"@medicine-wheel/mcp": "^4.5.4",
|
|
98
|
+
"@medicine-wheel/narrative-cluster": "^0.5.4",
|
|
99
|
+
"@medicine-wheel/narrative-engine": "^0.5.4",
|
|
100
|
+
"@medicine-wheel/ontology-core": "^0.5.4",
|
|
101
|
+
"@medicine-wheel/perception-layer": "^0.5.4",
|
|
102
|
+
"@medicine-wheel/prompt-decomposition": "^0.5.4",
|
|
103
|
+
"@medicine-wheel/relational-index": "^0.5.4",
|
|
104
|
+
"@medicine-wheel/relational-query": "^0.5.4",
|
|
105
|
+
"@medicine-wheel/session-reader": "^0.5.4",
|
|
106
|
+
"@medicine-wheel/storage-provider": "^0.5.4",
|
|
107
|
+
"@medicine-wheel/transformation-tracker": "^0.5.4",
|
|
108
|
+
"@medicine-wheel/ui-components": "^0.5.4",
|
|
103
109
|
"@neondatabase/serverless": "^0.10.0",
|
|
104
110
|
"@xyflow/react": "^12.3.0",
|
|
105
111
|
"clsx": "^2.1.1",
|
|
112
|
+
"js-yaml": "^4.3.0",
|
|
106
113
|
"lucide-react": "^0.475.0",
|
|
107
114
|
"next": "^15.3.0",
|
|
108
115
|
"next-themes": "^0.4.6",
|
|
@@ -115,6 +122,7 @@
|
|
|
115
122
|
},
|
|
116
123
|
"devDependencies": {
|
|
117
124
|
"@tailwindcss/postcss": "^4.1.0",
|
|
125
|
+
"@types/js-yaml": "^4.0.9",
|
|
118
126
|
"@types/node": "^22.0.0",
|
|
119
127
|
"@types/react": "^19.0.0",
|
|
120
128
|
"@types/react-dom": "^19.0.0",
|
|
@@ -122,5 +130,9 @@
|
|
|
122
130
|
"tailwindcss": "^4.1.0",
|
|
123
131
|
"typescript": "^5.7.0",
|
|
124
132
|
"vitest": "^4.1.8"
|
|
133
|
+
},
|
|
134
|
+
"repository": {
|
|
135
|
+
"type": "git",
|
|
136
|
+
"url": "git+https://github.com/jgwill/medicine-wheel.git"
|
|
125
137
|
}
|
|
126
138
|
}
|