@openparachute/vault 0.6.5-rc.9 → 0.6.5
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/.parachute/module.json +1 -0
- package/core/src/core.test.ts +7 -7
- package/core/src/mcp.ts +1 -1
- package/core/src/schema.ts +5 -5
- package/core/src/seed-packs.test.ts +222 -56
- package/core/src/seed-packs.ts +334 -70
- package/core/src/tag-hierarchy.ts +1 -1
- package/core/src/tag-schemas.ts +2 -2
- package/core/src/types.ts +1 -1
- package/package.json +1 -1
- package/src/admin-spa.ts +2 -1
- package/src/auth.ts +1 -1
- package/src/cli.ts +317 -53
- package/src/export-watch.ts +1 -1
- package/src/live-frame-parity.test.ts +201 -0
- package/src/module-manifest.ts +8 -0
- package/src/onboarding-seed.test.ts +82 -20
- package/src/onboarding-seed.ts +2 -2
- package/src/routes.ts +3 -3
- package/src/routing.test.ts +2 -2
- package/src/routing.ts +18 -6
- package/src/self-register.test.ts +19 -0
- package/src/self-register.ts +6 -1
- package/src/server.ts +56 -0
- package/src/services-manifest.ts +8 -0
- package/src/subscriptions.ts +100 -42
- package/src/tag-scope.ts +3 -3
- package/src/test-support/live-frame-corpus.ts +72 -0
- package/src/token-store.ts +2 -2
- package/src/transcription/build.test.ts +86 -5
- package/src/transcription/build.ts +87 -7
- package/src/transcription/capability.test.ts +25 -0
- package/src/transcription/capability.ts +27 -2
- package/src/transcription/download.test.ts +101 -0
- package/src/transcription/download.ts +71 -0
- package/src/transcription/install-python.test.ts +366 -0
- package/src/transcription/install-python.ts +471 -0
- package/src/transcription/providers/onnx-asr.test.ts +229 -0
- package/src/transcription/providers/onnx-asr.ts +239 -0
- package/src/transcription/providers/parakeet-mlx.test.ts +290 -0
- package/src/transcription/providers/parakeet-mlx.ts +242 -0
- package/src/transcription/select.test.ts +166 -1
- package/src/transcription/select.ts +234 -1
- package/src/transcription/tiers.test.ts +197 -0
- package/src/transcription/tiers.ts +184 -0
- package/src/vault-create.test.ts +19 -14
- package/src/vault.test.ts +1 -1
- package/src/ws-server.ts +408 -0
- package/src/ws-subscribe.test.ts +474 -0
- package/src/ws-subscribe.ts +242 -0
- package/src/subscribe.test.ts +0 -609
- package/src/subscribe.ts +0 -248
package/.parachute/module.json
CHANGED
package/core/src/core.test.ts
CHANGED
|
@@ -4046,7 +4046,7 @@ describe("query-notes link expansion", async () => {
|
|
|
4046
4046
|
});
|
|
4047
4047
|
|
|
4048
4048
|
// ---------------------------------------------------------------------------
|
|
4049
|
-
// Tag hierarchy via tags.parent_names (post-v14,
|
|
4049
|
+
// Tag hierarchy via tags.parent_names (post-v14, docs/contracts/tag-data-model.md)
|
|
4050
4050
|
// ---------------------------------------------------------------------------
|
|
4051
4051
|
|
|
4052
4052
|
describe("tag hierarchy (tags.parent_names)", async () => {
|
|
@@ -5107,7 +5107,7 @@ describe("schema inheritance via parent_names (vault#270)", async () => {
|
|
|
5107
5107
|
});
|
|
5108
5108
|
});
|
|
5109
5109
|
|
|
5110
|
-
describe("expandTagsWithDescendants (tag-scoped tokens —
|
|
5110
|
+
describe("expandTagsWithDescendants (tag-scoped tokens — docs/contracts/tag-scoped-tokens.md)", async () => {
|
|
5111
5111
|
it("returns the union of root + every descendant per tags.parent_names", async () => {
|
|
5112
5112
|
await store.upsertTagRecord("health/food", { parent_names: ["health"] });
|
|
5113
5113
|
await store.upsertTagRecord("health/food/breakfast", { parent_names: ["health/food"] });
|
|
@@ -5143,10 +5143,10 @@ describe("expandTagsWithDescendants (tag-scoped tokens — patterns/tag-scoped-t
|
|
|
5143
5143
|
});
|
|
5144
5144
|
|
|
5145
5145
|
// ---------------------------------------------------------------------------
|
|
5146
|
-
// Tag record API —
|
|
5146
|
+
// Tag record API — docs/contracts/tag-data-model.md
|
|
5147
5147
|
// ---------------------------------------------------------------------------
|
|
5148
5148
|
|
|
5149
|
-
describe("tag record API (
|
|
5149
|
+
describe("tag record API (docs/contracts/tag-data-model.md)", async () => {
|
|
5150
5150
|
it("upsertTagRecord persists description + fields + relationships + parent_names", async () => {
|
|
5151
5151
|
await store.upsertTagRecord("project", {
|
|
5152
5152
|
description: "long-running deliverable",
|
|
@@ -5430,7 +5430,7 @@ describe("tag record API (patterns/tag-data-model.md)", async () => {
|
|
|
5430
5430
|
});
|
|
5431
5431
|
|
|
5432
5432
|
// ---------------------------------------------------------------------------
|
|
5433
|
-
// Schema migration v13 → v14 —
|
|
5433
|
+
// Schema migration v13 → v14 — docs/contracts/tag-data-model.md
|
|
5434
5434
|
// ---------------------------------------------------------------------------
|
|
5435
5435
|
|
|
5436
5436
|
describe("schema migration v13 → v14", async () => {
|
|
@@ -5809,7 +5809,7 @@ describe("schema migration v15 → v16", async () => {
|
|
|
5809
5809
|
});
|
|
5810
5810
|
|
|
5811
5811
|
// ---------------------------------------------------------------------------
|
|
5812
|
-
// Tag-scope auth post-v14 —
|
|
5812
|
+
// Tag-scope auth post-v14 — docs/contracts/tag-scoped-tokens.md
|
|
5813
5813
|
// ---------------------------------------------------------------------------
|
|
5814
5814
|
|
|
5815
5815
|
describe("tag-scope auth (post-v14 hierarchy)", async () => {
|
|
@@ -5824,7 +5824,7 @@ describe("tag-scope auth (post-v14 hierarchy)", async () => {
|
|
|
5824
5824
|
});
|
|
5825
5825
|
|
|
5826
5826
|
it("orphan sub-tag fallback: token for `health` still sees `#health/food` even with no declared hierarchy", async () => {
|
|
5827
|
-
// Per
|
|
5827
|
+
// Per docs/contracts/tag-scoped-tokens.md §Storage details, the auth check
|
|
5828
5828
|
// also splits on '/' and matches the root verbatim against the raw
|
|
5829
5829
|
// allowlist. This survives the v14 source-of-truth swap because the
|
|
5830
5830
|
// fallback lives in src/tag-scope.ts, not in the resolver.
|
package/core/src/mcp.ts
CHANGED
|
@@ -1408,7 +1408,7 @@ Write-attribution (vault#298): every result carries \`createdBy\`/\`createdVia\`
|
|
|
1408
1408
|
{
|
|
1409
1409
|
name: "update-tag",
|
|
1410
1410
|
requiredVerb: "write",
|
|
1411
|
-
description: "Create or update a tag's identity row: description, indexed-field schemas, relationship-vocabulary map, and hierarchy parents. If the tag doesn't exist, it's created. Fields are merged (new keys added, existing keys replaced); relationships and parent_names are replaced wholesale when provided. Pass null for fields/relationships/parent_names to clear that column. See parachute-
|
|
1411
|
+
description: "Create or update a tag's identity row: description, indexed-field schemas, relationship-vocabulary map, and hierarchy parents. If the tag doesn't exist, it's created. Fields are merged (new keys added, existing keys replaced); relationships and parent_names are replaced wholesale when provided. Pass null for fields/relationships/parent_names to clear that column. See parachute-vault/docs/contracts/tag-data-model.md.",
|
|
1412
1412
|
inputSchema: {
|
|
1413
1413
|
type: "object",
|
|
1414
1414
|
properties: {
|
package/core/src/schema.ts
CHANGED
|
@@ -41,7 +41,7 @@ CREATE TABLE IF NOT EXISTS notes (
|
|
|
41
41
|
|
|
42
42
|
-- Tags: first-class identity carrying schema, hierarchy, and typed-link
|
|
43
43
|
-- declarations. One row per tag; no notes-as-config sidecars for these
|
|
44
|
-
-- concerns. See
|
|
44
|
+
-- concerns. See docs/contracts/tag-data-model.md.
|
|
45
45
|
--
|
|
46
46
|
-- description — human-readable blurb (markdown).
|
|
47
47
|
-- fields — JSON: indexed metadata field declarations per
|
|
@@ -136,7 +136,7 @@ CREATE TABLE IF NOT EXISTS indexed_fields (
|
|
|
136
136
|
-- scoped_tags is a JSON-encoded array of root tag names that constrain the
|
|
137
137
|
-- token's effective access (intersection with the scopes column). NULL
|
|
138
138
|
-- means unscoped — full vault access per scopes. Introduced in v13 per
|
|
139
|
-
--
|
|
139
|
+
-- docs/contracts/tag-scoped-tokens.md. Hierarchy expansion is applied at auth
|
|
140
140
|
-- time via getTagDescendants; the column stores root names only.
|
|
141
141
|
--
|
|
142
142
|
-- vault_name (v16) binds the token to a single vault. NULL means the
|
|
@@ -443,7 +443,7 @@ export function initSchema(db: Database): void {
|
|
|
443
443
|
// Migrate v13 → v14: tag-data-model reshape. Augment `tags` row with
|
|
444
444
|
// description/fields/relationships/parent_names/timestamps; copy data
|
|
445
445
|
// from the v6-era tag_schemas sidecar and from `_tags/<name>` config
|
|
446
|
-
// notes; drop tag_schemas after copy. See
|
|
446
|
+
// notes; drop tag_schemas after copy. See docs/contracts/tag-data-model.md.
|
|
447
447
|
migrateToV14(db);
|
|
448
448
|
|
|
449
449
|
// Migrate v14 → v15: retire the `_schemas/<name>` and `_schema_defaults`
|
|
@@ -650,7 +650,7 @@ function migrateToV12(db: Database): void {
|
|
|
650
650
|
* (current full-vault behavior); a JSON array of root tag names narrows the
|
|
651
651
|
* token's access to notes carrying one of those tags or a sub-tag thereof
|
|
652
652
|
* (hierarchy expansion via getTagDescendants at auth time). See
|
|
653
|
-
*
|
|
653
|
+
* docs/contracts/tag-scoped-tokens.md.
|
|
654
654
|
*/
|
|
655
655
|
function migrateToV13(db: Database): void {
|
|
656
656
|
if (hasTable(db, "tokens") && !hasColumn(db, "tokens", "scoped_tags")) {
|
|
@@ -659,7 +659,7 @@ function migrateToV13(db: Database): void {
|
|
|
659
659
|
}
|
|
660
660
|
|
|
661
661
|
/**
|
|
662
|
-
* Migrate v13 → v14: tag-data-model reshape (
|
|
662
|
+
* Migrate v13 → v14: tag-data-model reshape (docs/contracts/tag-data-model.md).
|
|
663
663
|
*
|
|
664
664
|
* Augments the `tags` table with five new columns and one timestamp pair,
|
|
665
665
|
* then copies pre-existing data from two notes-as-config sidecars:
|
|
@@ -1,35 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Content + registry pins for the named seed packs (core/src/seed-packs.ts).
|
|
3
3
|
*
|
|
4
|
-
* Pure content tests — no DB. The applier's behavior (idempotence, seeding
|
|
5
|
-
* is exercised store-level in
|
|
6
|
-
* src/add-pack.test.ts.
|
|
4
|
+
* Pure content tests — no DB. The applier's behavior (idempotence, seeding,
|
|
5
|
+
* tag/metadata write-through) is exercised store-level in
|
|
6
|
+
* src/onboarding-seed.test.ts and CLI-level in src/add-pack.test.ts.
|
|
7
7
|
*
|
|
8
|
-
* The load-bearing
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
8
|
+
* The load-bearing pins:
|
|
9
|
+
* - the `welcome` pack's tags must CONTAIN notes-ui's NOTES_REQUIRED_SCHEMA
|
|
10
|
+
* byte-for-byte — the Notes PWA's connect-time audit compares `description`
|
|
11
|
+
* verbatim, and its schema banner only clears when the capture tag genuinely
|
|
12
|
+
* carries the semantics Notes declares. (Containment, not equality: the
|
|
13
|
+
* welcome pack now also ships the #guide tag.)
|
|
14
|
+
* - `#guide` is the vault's skill-file tag (no schema — guides are AI-first
|
|
15
|
+
* and human-readable markdown, so no per-guide audience field). Every seeded
|
|
16
|
+
* guide note is tagged `#guide`.
|
|
17
|
+
* - the five-guide welcome ring links into a connected web with no dangling
|
|
18
|
+
* wikilinks across the default seed.
|
|
14
19
|
*/
|
|
15
20
|
|
|
16
21
|
import { describe, test, expect } from "bun:test";
|
|
17
22
|
import {
|
|
18
23
|
applySeedPack,
|
|
24
|
+
CAPTURE_ANYTHING_PATH,
|
|
19
25
|
CONNECT_AI_PATH,
|
|
26
|
+
DEFAULT_VAULT_DESCRIPTION,
|
|
27
|
+
IMPORTED_VAULT_DESCRIPTION,
|
|
28
|
+
YOURS_TO_KEEP_PATH,
|
|
20
29
|
GETTING_STARTED_CONTENT,
|
|
21
30
|
GETTING_STARTED_PACK,
|
|
22
31
|
GETTING_STARTED_PATH,
|
|
23
32
|
getSeedPack,
|
|
33
|
+
GUIDE_TAG,
|
|
24
34
|
listSeedPacks,
|
|
25
35
|
NOTES_REQUIRED_TAGS,
|
|
26
36
|
SEED_PACK_NAMES,
|
|
27
37
|
SURFACE_STARTER_CONTENT,
|
|
28
38
|
SURFACE_STARTER_PACK,
|
|
29
39
|
SURFACE_STARTER_PATH,
|
|
30
|
-
|
|
40
|
+
TAGS_GRAPH_PATH,
|
|
31
41
|
WELCOME_PATH,
|
|
32
42
|
welcomePack,
|
|
43
|
+
YOURS_TO_KEEP_PATH,
|
|
44
|
+
type SeedPack,
|
|
45
|
+
type SeedPackNote,
|
|
33
46
|
} from "./seed-packs.ts";
|
|
34
47
|
import * as onboardingShim from "./onboarding.ts";
|
|
35
48
|
|
|
@@ -47,17 +60,59 @@ const NOTES_UI_REQUIRED_SCHEMA_TAGS = [
|
|
|
47
60
|
},
|
|
48
61
|
];
|
|
49
62
|
|
|
50
|
-
|
|
51
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Extract every REAL `[[wikilink]]` target from note content — mirroring the
|
|
65
|
+
* store's parser (core/src/wikilinks.ts), which ignores wikilinks inside
|
|
66
|
+
* fenced/inline code. So an illustrative `[[wikilinks]]` in a code span (as in
|
|
67
|
+
* the Getting Started guide) is not counted — it never becomes a link.
|
|
68
|
+
*/
|
|
69
|
+
function wikilinkTargets(content: string): string[] {
|
|
70
|
+
const stripped = content
|
|
71
|
+
.replace(/```[\s\S]*?```/g, (m) => " ".repeat(m.length)) // fenced code
|
|
72
|
+
.replace(/`[^`]*`/g, (m) => " ".repeat(m.length)); // inline code
|
|
73
|
+
return [...stripped.matchAll(/\[\[([^\]]+)\]\]/g)].map((m) => m[1]!);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** A note in a pack, by path (fails loudly if absent). */
|
|
77
|
+
function noteAt(pack: SeedPack, path: string): SeedPackNote {
|
|
78
|
+
const note = pack.notes.find((n) => n.path === path);
|
|
79
|
+
if (!note) throw new Error(`no note at path ${path}`);
|
|
80
|
+
return note;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
describe("guide tag — the vault's skill-file tag", () => {
|
|
84
|
+
test("GUIDE_TAG is the skill-file tag with no per-guide audience schema", () => {
|
|
85
|
+
expect(GUIDE_TAG.name).toBe("guide");
|
|
86
|
+
expect(GUIDE_TAG.description).toContain("skill file");
|
|
87
|
+
// written_for was dropped (2026-07-06): guides are AI-first + human-readable
|
|
88
|
+
// markdown, so a per-guide audience field earns nothing. No schema on the tag.
|
|
89
|
+
expect(GUIDE_TAG.fields).toBeUndefined();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe("welcome pack — notes-ui schema parity (containment)", () => {
|
|
95
|
+
test("welcome tags CONTAIN notes-ui's NOTES_REQUIRED_SCHEMA byte-for-byte", () => {
|
|
96
|
+
// The independent copy still matches, and the welcome pack still ships it.
|
|
52
97
|
expect(NOTES_REQUIRED_TAGS).toEqual(NOTES_UI_REQUIRED_SCHEMA_TAGS);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
expect(
|
|
98
|
+
for (const req of NOTES_UI_REQUIRED_SCHEMA_TAGS) {
|
|
99
|
+
const found = welcomePack().tags.find((t) => t.name === req.name);
|
|
100
|
+
expect(found).toBeDefined();
|
|
101
|
+
// Field-level byte pins (toEqual ignores key order; these don't).
|
|
102
|
+
expect(found!.name).toBe(req.name);
|
|
103
|
+
expect(found!.description).toBe(req.description);
|
|
104
|
+
expect(found!.parent_names).toBeUndefined();
|
|
58
105
|
}
|
|
59
106
|
});
|
|
60
107
|
|
|
108
|
+
test("welcome tags = capture + guide (the guide tag rides along)", () => {
|
|
109
|
+
expect(welcomePack().tags.map((t) => t.name)).toEqual([
|
|
110
|
+
"capture",
|
|
111
|
+
"guide",
|
|
112
|
+
]);
|
|
113
|
+
expect(welcomePack().tags).toContain(GUIDE_TAG);
|
|
114
|
+
});
|
|
115
|
+
|
|
61
116
|
test("exactly ONE capture tag — no subtype tags, no parent_names (2026-07-03)", () => {
|
|
62
117
|
// Entry method is metadata.source (text|voice), not taxonomy. The old
|
|
63
118
|
// capture/text + capture/voice subtypes must not creep back into the seed
|
|
@@ -68,84 +123,195 @@ describe("welcome pack — notes-ui schema parity", () => {
|
|
|
68
123
|
});
|
|
69
124
|
});
|
|
70
125
|
|
|
71
|
-
describe("welcome pack —
|
|
72
|
-
test("
|
|
126
|
+
describe("welcome pack — the five-guide ring", () => {
|
|
127
|
+
test("five guides at the ratified paths, in order", () => {
|
|
73
128
|
const pack = welcomePack();
|
|
74
129
|
expect(pack.name).toBe("welcome");
|
|
75
130
|
expect(pack.notes.map((n) => n.path)).toEqual([
|
|
76
131
|
WELCOME_PATH,
|
|
77
|
-
|
|
132
|
+
CAPTURE_ANYTHING_PATH,
|
|
133
|
+
TAGS_GRAPH_PATH,
|
|
78
134
|
CONNECT_AI_PATH,
|
|
135
|
+
YOURS_TO_KEEP_PATH,
|
|
79
136
|
]);
|
|
80
|
-
|
|
81
|
-
const [welcome, tryLinking, connectAi] = pack.notes;
|
|
82
|
-
expect(welcome!.content).toContain(`[[${TRY_LINKING_PATH}]]`);
|
|
83
|
-
expect(tryLinking!.content).toContain(`[[${WELCOME_PATH}]]`);
|
|
84
|
-
expect(connectAi!.content).toContain(`[[${WELCOME_PATH}]]`);
|
|
85
137
|
});
|
|
86
138
|
|
|
87
|
-
test("
|
|
88
|
-
|
|
89
|
-
// `welcomeNotes(consoleOrigin)` — the sibling cloud PR swaps that module
|
|
90
|
-
// for this pack, so the ported content must not drift.
|
|
91
|
-
const origin = "https://cloud.parachute.computer";
|
|
92
|
-
const pack = welcomePack({ consoleOrigin: origin });
|
|
139
|
+
test("the link web: welcome → all four; 2→3→4→5→1; connect → Getting Started", () => {
|
|
140
|
+
const pack = welcomePack();
|
|
93
141
|
|
|
94
|
-
|
|
142
|
+
// Welcome wikilinks to all four siblings.
|
|
143
|
+
const welcomeLinks = wikilinkTargets(noteAt(pack, WELCOME_PATH).content);
|
|
144
|
+
for (const target of [
|
|
145
|
+
CAPTURE_ANYTHING_PATH,
|
|
146
|
+
TAGS_GRAPH_PATH,
|
|
147
|
+
CONNECT_AI_PATH,
|
|
148
|
+
YOURS_TO_KEEP_PATH,
|
|
149
|
+
]) {
|
|
150
|
+
expect(welcomeLinks).toContain(target);
|
|
151
|
+
}
|
|
95
152
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
expect(pack.
|
|
153
|
+
// The chain forward: 2→3, 3→4, 4→5, 5→1.
|
|
154
|
+
expect(wikilinkTargets(noteAt(pack, CAPTURE_ANYTHING_PATH).content)).toContain(
|
|
155
|
+
TAGS_GRAPH_PATH,
|
|
156
|
+
);
|
|
157
|
+
expect(wikilinkTargets(noteAt(pack, TAGS_GRAPH_PATH).content)).toContain(
|
|
158
|
+
CONNECT_AI_PATH,
|
|
159
|
+
);
|
|
160
|
+
expect(wikilinkTargets(noteAt(pack, CONNECT_AI_PATH).content)).toContain(
|
|
161
|
+
YOURS_TO_KEEP_PATH,
|
|
162
|
+
);
|
|
163
|
+
expect(wikilinkTargets(noteAt(pack, YOURS_TO_KEEP_PATH).content)).toContain(
|
|
164
|
+
WELCOME_PATH,
|
|
165
|
+
);
|
|
101
166
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
167
|
+
// Connect your AI points the human at the AI-facing Getting Started note.
|
|
168
|
+
expect(wikilinkTargets(noteAt(pack, CONNECT_AI_PATH).content)).toContain(
|
|
169
|
+
GETTING_STARTED_PATH,
|
|
170
|
+
);
|
|
171
|
+
});
|
|
105
172
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
173
|
+
test("NO dangling wikilinks across the default seed (welcome + getting-started)", () => {
|
|
174
|
+
// Both packs auto-seed on create, so every [[target]] across their notes
|
|
175
|
+
// must resolve to a note one of them writes — including [[Getting Started]].
|
|
176
|
+
const seededPaths = new Set<string>([
|
|
177
|
+
...welcomePack().notes.map((n) => n.path),
|
|
178
|
+
...GETTING_STARTED_PACK.notes.map((n) => n.path),
|
|
179
|
+
]);
|
|
180
|
+
for (const pack of [welcomePack(), GETTING_STARTED_PACK]) {
|
|
181
|
+
for (const note of pack.notes) {
|
|
182
|
+
for (const target of wikilinkTargets(note.content)) {
|
|
183
|
+
expect(seededPaths.has(target)).toBe(true);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
109
187
|
});
|
|
110
188
|
|
|
111
|
-
test("
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
189
|
+
test("every welcome guide is tagged #guide", () => {
|
|
190
|
+
for (const note of welcomePack().notes) {
|
|
191
|
+
expect(note.tags).toContain("guide");
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("no seeded note is pinned (pinning is a user action, not seeded)", () => {
|
|
196
|
+
// The vestigial #pinned seed was dropped 2026-07-07 — it did nothing visible
|
|
197
|
+
// on a fresh vault. Pinning still works when a user pins a note in the app.
|
|
198
|
+
const pinned = welcomePack().notes.filter((n) => n.tags?.includes("pinned"));
|
|
199
|
+
expect(pinned).toEqual([]);
|
|
200
|
+
expect(noteAt(welcomePack(), WELCOME_PATH).tags).toEqual(["guide"]);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("content anchors: the ratified copy is present (guards body drift)", () => {
|
|
204
|
+
const pack = welcomePack();
|
|
205
|
+
expect(noteAt(pack, WELCOME_PATH).content).toContain("This vault is yours.");
|
|
206
|
+
expect(noteAt(pack, WELCOME_PATH).content).toContain("tagged #guide");
|
|
207
|
+
expect(noteAt(pack, CAPTURE_ANYTHING_PATH).content).toContain(
|
|
208
|
+
"The one habit that makes a vault work",
|
|
209
|
+
);
|
|
210
|
+
expect(noteAt(pack, TAGS_GRAPH_PATH).content).toContain(
|
|
211
|
+
"Notes and links come first. Tags come later",
|
|
212
|
+
);
|
|
213
|
+
expect(noteAt(pack, YOURS_TO_KEEP_PATH).content).toContain(
|
|
214
|
+
"the moral center of the",
|
|
215
|
+
);
|
|
216
|
+
// Yours to keep carries the self-host export command (backtick-wrapped).
|
|
217
|
+
expect(noteAt(pack, YOURS_TO_KEEP_PATH).content).toContain(
|
|
218
|
+
"`parachute-vault export <dir>`",
|
|
115
219
|
);
|
|
116
|
-
|
|
117
|
-
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("consoleOrigin renders both modes on the Connect-your-AI note", () => {
|
|
223
|
+
const origin = "https://cloud.parachute.computer";
|
|
224
|
+
|
|
225
|
+
// With origin: the console sentence names it, no `undefined`, no `at .`.
|
|
226
|
+
const withOrigin = noteAt(welcomePack({ consoleOrigin: origin }), CONNECT_AI_PATH);
|
|
227
|
+
expect(withOrigin.content).toContain(
|
|
228
|
+
`Grab the connection URL from your console at ${origin}.`,
|
|
229
|
+
);
|
|
230
|
+
expect(withOrigin.content).not.toContain("undefined");
|
|
231
|
+
|
|
232
|
+
// Without origin: the line stays generic — no baked loopback, no `at .`.
|
|
233
|
+
const generic = noteAt(welcomePack(), CONNECT_AI_PATH);
|
|
234
|
+
expect(generic.content).toContain(
|
|
235
|
+
"Grab the connection URL from your console.",
|
|
236
|
+
);
|
|
237
|
+
expect(generic.content).not.toContain("undefined");
|
|
238
|
+
expect(generic.content).not.toContain("at .");
|
|
239
|
+
expect(generic.content).not.toContain(origin);
|
|
118
240
|
});
|
|
119
241
|
});
|
|
120
242
|
|
|
121
243
|
describe("getting-started pack", () => {
|
|
122
|
-
test("carries the
|
|
244
|
+
test("carries the AI-facing guide note, tagged #guide", () => {
|
|
123
245
|
expect(GETTING_STARTED_PACK.name).toBe("getting-started");
|
|
124
|
-
|
|
246
|
+
// Declares the guide tag (self-sufficient — converges with the welcome pack).
|
|
247
|
+
expect(GETTING_STARTED_PACK.tags).toEqual([GUIDE_TAG]);
|
|
125
248
|
expect(GETTING_STARTED_PACK.notes).toEqual([
|
|
126
|
-
{
|
|
249
|
+
{
|
|
250
|
+
path: GETTING_STARTED_PATH,
|
|
251
|
+
tags: ["guide"],
|
|
252
|
+
content: GETTING_STARTED_CONTENT,
|
|
253
|
+
},
|
|
127
254
|
]);
|
|
128
255
|
});
|
|
129
256
|
|
|
257
|
+
test("the Getting Started body is byte-unchanged", () => {
|
|
258
|
+
// The AI-facing doctrine copy is not touched by the ring rewrite.
|
|
259
|
+
expect(noteAt(GETTING_STARTED_PACK, GETTING_STARTED_PATH).content).toBe(
|
|
260
|
+
GETTING_STARTED_CONTENT,
|
|
261
|
+
);
|
|
262
|
+
expect(GETTING_STARTED_CONTENT.startsWith("# Getting Started\n")).toBe(true);
|
|
263
|
+
});
|
|
264
|
+
|
|
130
265
|
test("no dangling [[Surface Starter]] wikilink — points at the add-pack flow instead", () => {
|
|
131
266
|
// Surface Starter is out of the default seed (ratified 2026-07-02), so the
|
|
132
267
|
// default-seeded guide must not carry a wikilink to a note that doesn't
|
|
133
268
|
// exist. It names the pack + the way to add it.
|
|
134
269
|
expect(GETTING_STARTED_CONTENT).not.toContain("[[Surface Starter]]");
|
|
135
270
|
expect(GETTING_STARTED_CONTENT).toContain("Surface Starter");
|
|
136
|
-
|
|
271
|
+
// Points at the add-pack flow (console affordance / CLI) without spelling
|
|
272
|
+
// out a terminal command — de-emphasise the CLI (Aaron 2026-07-06).
|
|
273
|
+
expect(GETTING_STARTED_CONTENT).toContain("add-pack");
|
|
137
274
|
// The surface packages are still named for discoverability.
|
|
138
275
|
expect(GETTING_STARTED_CONTENT).toContain("@openparachute/surface-client");
|
|
139
276
|
expect(GETTING_STARTED_CONTENT).toContain("@openparachute/surface-render");
|
|
140
277
|
});
|
|
278
|
+
|
|
279
|
+
test("Getting Started is onboarding-first + carries no dead CLI guidance (2026-07-06 rewrite)", () => {
|
|
280
|
+
// Two-part structure: onboarding conversation leads, mechanics reference follows.
|
|
281
|
+
expect(GETTING_STARTED_CONTENT).toContain("## Part 1 — Onboarding the person");
|
|
282
|
+
expect(GETTING_STARTED_CONTENT).toContain("## Part 2 — Vault mechanics");
|
|
283
|
+
// Points the person at the five human guides by name (all resolve to seeds).
|
|
284
|
+
for (const g of [WELCOME_PATH, CAPTURE_ANYTHING_PATH, TAGS_GRAPH_PATH, CONNECT_AI_PATH, YOURS_TO_KEEP_PATH]) {
|
|
285
|
+
expect(GETTING_STARTED_CONTENT).toContain("[[" + g + "]]");
|
|
286
|
+
}
|
|
287
|
+
// The old live bug: it told cloud AIs to run a CLI that doesn't exist there.
|
|
288
|
+
expect(GETTING_STARTED_CONTENT).not.toContain("parachute-vault import");
|
|
289
|
+
// Data-in leads with the import UI + MCP bridge, not the terminal.
|
|
290
|
+
expect(GETTING_STARTED_CONTENT).toContain("Import button");
|
|
291
|
+
expect(GETTING_STARTED_CONTENT).toContain("over MCP");
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test("default vault-description constants orient + tell the AI to self-replace them", () => {
|
|
295
|
+
for (const d of [DEFAULT_VAULT_DESCRIPTION, IMPORTED_VAULT_DESCRIPTION]) {
|
|
296
|
+
expect(d.length).toBeGreaterThan(100);
|
|
297
|
+
expect(d).toContain("Getting Started");
|
|
298
|
+
expect(d).toContain("vault-info { description:"); // the self-replace instruction
|
|
299
|
+
}
|
|
300
|
+
expect(DEFAULT_VAULT_DESCRIPTION).toContain("brand-new");
|
|
301
|
+
expect(IMPORTED_VAULT_DESCRIPTION).toContain("imported");
|
|
302
|
+
});
|
|
141
303
|
});
|
|
142
304
|
|
|
143
305
|
describe("surface-starter pack", () => {
|
|
144
|
-
test("carries the surface guide
|
|
306
|
+
test("carries the surface guide, tagged #guide (opt-in, not default)", () => {
|
|
145
307
|
expect(SURFACE_STARTER_PACK.name).toBe("surface-starter");
|
|
146
|
-
expect(SURFACE_STARTER_PACK.tags).toEqual([]);
|
|
308
|
+
expect(SURFACE_STARTER_PACK.tags).toEqual([GUIDE_TAG]);
|
|
147
309
|
expect(SURFACE_STARTER_PACK.notes).toEqual([
|
|
148
|
-
{
|
|
310
|
+
{
|
|
311
|
+
path: SURFACE_STARTER_PATH,
|
|
312
|
+
tags: ["guide"],
|
|
313
|
+
content: SURFACE_STARTER_CONTENT,
|
|
314
|
+
},
|
|
149
315
|
]);
|
|
150
316
|
expect(SURFACE_STARTER_PACK.description).toContain("not seeded by default");
|
|
151
317
|
});
|