forge-openclaw-plugin 0.2.49 → 0.2.51

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/index.html CHANGED
@@ -13,14 +13,14 @@
13
13
  />
14
14
  <link rel="icon" type="image/png" href="/forge/assets/favicon-BCHm9dUV.ico" />
15
15
  <link rel="alternate icon" href="/forge/assets/favicon-BCHm9dUV.ico" />
16
- <script type="module" crossorigin src="/forge/assets/index-BAmEvOXb.js"></script>
16
+ <script type="module" crossorigin src="/forge/assets/index-DX8RiahO.js"></script>
17
17
  <link rel="modulepreload" crossorigin href="/forge/assets/vendor-D_NZFJze.js">
18
18
  <link rel="modulepreload" crossorigin href="/forge/assets/board-CAszQU7Y.js">
19
19
  <link rel="modulepreload" crossorigin href="/forge/assets/ui-B5MjRjKe.js">
20
20
  <link rel="modulepreload" crossorigin href="/forge/assets/motion-CU5aNClV.js">
21
21
  <link rel="modulepreload" crossorigin href="/forge/assets/table-CK0KcPYW.js">
22
22
  <link rel="stylesheet" crossorigin href="/forge/assets/vendor-DT3pnAKJ.css">
23
- <link rel="stylesheet" crossorigin href="/forge/assets/index-2_tuemtU.css">
23
+ <link rel="stylesheet" crossorigin href="/forge/assets/index-gthTrgvO.css">
24
24
  </head>
25
25
  <body class="bg-canvas text-ink antialiased">
26
26
  <div id="root"></div>
@@ -34,11 +34,13 @@ export const FORGE_SUPPORTED_PLUGIN_API_ROUTES = [
34
34
  { method: "GET", path: "/api/v1/movement/all-time", purpose: "health" },
35
35
  { method: "GET", path: "/api/v1/movement/timeline", purpose: "health" },
36
36
  { method: "GET", path: "/api/v1/movement/places", purpose: "health" },
37
+ { method: "GET", path: "/api/v1/movement/boxes/:id", purpose: "health" },
37
38
  { method: "POST", path: "/api/v1/movement/places", purpose: "health" },
38
39
  { method: "PATCH", path: "/api/v1/movement/places/:id", purpose: "health" },
39
40
  { method: "GET", path: "/api/v1/movement/trips/:id", purpose: "health" },
40
41
  { method: "POST", path: "/api/v1/movement/selection", purpose: "health" },
41
42
  { method: "GET", path: "/api/v1/movement/settings", purpose: "health" },
43
+ { method: "PATCH", path: "/api/v1/movement/settings", purpose: "health" },
42
44
  { method: "POST", path: "/api/v1/movement/user-boxes", purpose: "health" },
43
45
  {
44
46
  method: "POST",
@@ -50,18 +52,30 @@ export const FORGE_SUPPORTED_PLUGIN_API_ROUTES = [
50
52
  path: "/api/v1/movement/user-boxes/:id",
51
53
  purpose: "health"
52
54
  },
55
+ {
56
+ method: "DELETE",
57
+ path: "/api/v1/movement/user-boxes/:id",
58
+ purpose: "health"
59
+ },
53
60
  {
54
61
  method: "POST",
55
62
  path: "/api/v1/movement/automatic-boxes/:id/invalidate",
56
63
  purpose: "health"
57
64
  },
58
65
  { method: "PATCH", path: "/api/v1/movement/stays/:id", purpose: "health" },
66
+ { method: "DELETE", path: "/api/v1/movement/stays/:id", purpose: "health" },
59
67
  { method: "PATCH", path: "/api/v1/movement/trips/:id", purpose: "health" },
68
+ { method: "DELETE", path: "/api/v1/movement/trips/:id", purpose: "health" },
60
69
  {
61
70
  method: "PATCH",
62
71
  path: "/api/v1/movement/trips/:id/points/:pointId",
63
72
  purpose: "health"
64
73
  },
74
+ {
75
+ method: "DELETE",
76
+ path: "/api/v1/movement/trips/:id/points/:pointId",
77
+ purpose: "health"
78
+ },
65
79
  { method: "GET", path: "/api/v1/life-force", purpose: "health" },
66
80
  { method: "PATCH", path: "/api/v1/life-force/profile", purpose: "health" },
67
81
  {
@@ -198,6 +198,12 @@ export const FORGE_PLUGIN_ROUTE_GROUPS = [
198
198
  upstreamPath: "/api/v1/movement/places",
199
199
  target: (_match, url) => passthroughSearch("/api/v1/movement/places", url)
200
200
  },
201
+ {
202
+ method: "GET",
203
+ pattern: /^\/forge\/v1\/movement\/boxes\/([^/]+)$/,
204
+ upstreamPath: "/api/v1/movement/boxes/:id",
205
+ target: (match, url) => passthroughSearch(`/api/v1/movement/boxes/${match[1]}`, url)
206
+ },
201
207
  {
202
208
  method: "POST",
203
209
  pattern: /^\/forge\/v1\/movement\/places$/,
@@ -233,6 +239,14 @@ export const FORGE_PLUGIN_ROUTE_GROUPS = [
233
239
  upstreamPath: "/api/v1/movement/settings",
234
240
  target: (_match, url) => passthroughSearch("/api/v1/movement/settings", url)
235
241
  },
242
+ {
243
+ method: "PATCH",
244
+ pattern: /^\/forge\/v1\/movement\/settings$/,
245
+ upstreamPath: "/api/v1/movement/settings",
246
+ requestBody: "json",
247
+ requiresToken: true,
248
+ target: (_match, url) => passthroughSearch("/api/v1/movement/settings", url)
249
+ },
236
250
  {
237
251
  method: "POST",
238
252
  pattern: /^\/forge\/v1\/movement\/user-boxes$/,
@@ -257,6 +271,13 @@ export const FORGE_PLUGIN_ROUTE_GROUPS = [
257
271
  requiresToken: true,
258
272
  target: (match, url) => passthroughSearch(`/api/v1/movement/user-boxes/${match[1]}`, url)
259
273
  },
274
+ {
275
+ method: "DELETE",
276
+ pattern: /^\/forge\/v1\/movement\/user-boxes\/([^/]+)$/,
277
+ upstreamPath: "/api/v1/movement/user-boxes/:id",
278
+ requiresToken: true,
279
+ target: (match, url) => passthroughSearch(`/api/v1/movement/user-boxes/${match[1]}`, url)
280
+ },
260
281
  {
261
282
  method: "POST",
262
283
  pattern: /^\/forge\/v1\/movement\/automatic-boxes\/([^/]+)\/invalidate$/,
@@ -273,6 +294,13 @@ export const FORGE_PLUGIN_ROUTE_GROUPS = [
273
294
  requiresToken: true,
274
295
  target: (match, url) => passthroughSearch(`/api/v1/movement/stays/${match[1]}`, url)
275
296
  },
297
+ {
298
+ method: "DELETE",
299
+ pattern: /^\/forge\/v1\/movement\/stays\/([^/]+)$/,
300
+ upstreamPath: "/api/v1/movement/stays/:id",
301
+ requiresToken: true,
302
+ target: (match, url) => passthroughSearch(`/api/v1/movement/stays/${match[1]}`, url)
303
+ },
276
304
  {
277
305
  method: "PATCH",
278
306
  pattern: /^\/forge\/v1\/movement\/trips\/([^/]+)$/,
@@ -281,6 +309,13 @@ export const FORGE_PLUGIN_ROUTE_GROUPS = [
281
309
  requiresToken: true,
282
310
  target: (match, url) => passthroughSearch(`/api/v1/movement/trips/${match[1]}`, url)
283
311
  },
312
+ {
313
+ method: "DELETE",
314
+ pattern: /^\/forge\/v1\/movement\/trips\/([^/]+)$/,
315
+ upstreamPath: "/api/v1/movement/trips/:id",
316
+ requiresToken: true,
317
+ target: (match, url) => passthroughSearch(`/api/v1/movement/trips/${match[1]}`, url)
318
+ },
284
319
  {
285
320
  method: "PATCH",
286
321
  pattern: /^\/forge\/v1\/movement\/trips\/([^/]+)\/points\/([^/]+)$/,
@@ -288,6 +323,13 @@ export const FORGE_PLUGIN_ROUTE_GROUPS = [
288
323
  requestBody: "json",
289
324
  requiresToken: true,
290
325
  target: (match, url) => passthroughSearch(`/api/v1/movement/trips/${match[1]}/points/${match[2]}`, url)
326
+ },
327
+ {
328
+ method: "DELETE",
329
+ pattern: /^\/forge\/v1\/movement\/trips\/([^/]+)\/points\/([^/]+)$/,
330
+ upstreamPath: "/api/v1/movement/trips/:id/points/:pointId",
331
+ requiresToken: true,
332
+ target: (match, url) => passthroughSearch(`/api/v1/movement/trips/${match[1]}/points/${match[2]}`, url)
291
333
  }
292
334
  ]
293
335
  },
@@ -344,7 +344,7 @@ export function registerForgePluginTools(api, config) {
344
344
  api.registerTool({
345
345
  name: "forge_upsert_wiki_page",
346
346
  label: "Forge Upsert Wiki Page",
347
- description: "Create a new wiki page or update an existing one through the file-backed wiki surface.",
347
+ description: "Create a new wiki page or update an existing one through the SQLite-backed wiki surface.",
348
348
  parameters: wikiPageMutationSchema(),
349
349
  async execute(_toolCallId, params) {
350
350
  const typed = (params ?? {});
@@ -375,8 +375,8 @@ export function registerForgePluginTools(api, config) {
375
375
  });
376
376
  registerWriteTool(api, config, {
377
377
  name: "forge_sync_wiki_vault",
378
- label: "Forge Sync Wiki Vault",
379
- description: "Resync Markdown files from the local wiki vault into Forge metadata.",
378
+ label: "Forge Refresh Wiki Indexes",
379
+ description: "Rebuild SQLite wiki search, link, and metadata indexes.",
380
380
  parameters: Type.Object({
381
381
  spaceId: optionalString()
382
382
  }),
@@ -57,7 +57,7 @@ VALUES (
57
57
  'wiki_space_shared',
58
58
  'shared',
59
59
  'Shared Forge Memory',
60
- 'Shared wiki space for file-backed Forge knowledge.',
60
+ 'Shared wiki space for SQLite-backed Forge knowledge.',
61
61
  NULL,
62
62
  'shared',
63
63
  CURRENT_TIMESTAMP,
@@ -0,0 +1,8 @@
1
+ UPDATE wiki_spaces
2
+ SET description = 'Shared wiki space for SQLite-backed Forge knowledge.'
3
+ WHERE id = 'wiki_space_shared'
4
+ AND description != 'Shared wiki space for SQLite-backed Forge knowledge.';
5
+
6
+ UPDATE notes
7
+ SET source_path = ''
8
+ WHERE source_path != '';
@@ -2643,7 +2643,7 @@ const AGENT_ONBOARDING_ENTITY_CATALOG = [
2643
2643
  }),
2644
2644
  enrichOnboardingEntityGuide({
2645
2645
  entityType: "wiki_page",
2646
- purpose: "A file-backed Forge wiki page or evidence page.",
2646
+ purpose: "A SQLite-backed Forge wiki page or evidence page.",
2647
2647
  minimumCreateFields: ["title", "contentMarkdown"],
2648
2648
  relationshipRules: [
2649
2649
  "Wiki pages live on the wiki surface and use specialized page upsert routes rather than batch CRUD.",
@@ -2772,6 +2772,8 @@ const AGENT_ONBOARDING_CONVERSATION_RULES = [
2772
2772
  "When useful, help the user name, define, and connect the record in that order: offer a working label, clarify what belongs inside it, then ask about links only after the record itself feels steady.",
2773
2773
  "When the meaning is clearer than the wording, offer a tentative title or formulation yourself and invite correction instead of forcing the user to wordsmith alone.",
2774
2774
  "For direct update or review requests, the next question should usually narrow the saved object, timeframe, or route family instead of reopening the whole meaning-making arc.",
2775
+ "For updates, start with the smallest thing that now feels wrong, newly true, or newly visible rather than restarting the whole story.",
2776
+ "For review requests, ask what practical question the user wants the read to answer before you ask for more scope.",
2775
2777
  "The opening question should help the user understand what they are actually trying to save, decide, review, or change, not make them perform the schema out loud.",
2776
2778
  "If the user already named the exact correction in usable language, confirm only the missing scope, timing, or route-selecting detail that still matters, then act.",
2777
2779
  "Once a specialized-surface lane is clear, speak in route-relevant nouns such as timeline, overlay, weekday template, published output, run detail, or node result instead of generic record language.",
@@ -3550,7 +3552,7 @@ const AGENT_ONBOARDING_TOOL_INPUT_CATALOG = [
3550
3552
  requiredFields: [],
3551
3553
  notes: [
3552
3554
  "Semantic search is optional and profile-driven.",
3553
- "The wiki is file-first, so spaces map to local vault directories."
3555
+ "The wiki is SQLite-backed, so pages and evidence live in Forge's database."
3554
3556
  ],
3555
3557
  example: "{}"
3556
3558
  },
@@ -3592,13 +3594,13 @@ const AGENT_ONBOARDING_TOOL_INPUT_CATALOG = [
3592
3594
  },
3593
3595
  {
3594
3596
  toolName: "forge_upsert_wiki_page",
3595
- summary: "Create a new wiki page or update an existing one through the file-backed wiki surface.",
3597
+ summary: "Create a new wiki page or update an existing one through the SQLite-backed wiki surface.",
3596
3598
  whenToUse: "Use when the user explicitly wants wiki memory persisted or reorganized.",
3597
3599
  inputShape: '{ pageId?: string, kind?: "wiki"|"evidence", title: string, slug?: string, summary?: string, aliases?: string[], contentMarkdown: string, author?: string|null, tags?: string[], spaceId?: string, frontmatter?: object, links?: Array<{ entityType, entityId, anchorKey? }> }',
3598
3600
  requiredFields: ["title", "contentMarkdown"],
3599
3601
  notes: [
3600
3602
  "When pageId is omitted, Forge creates a new page.",
3601
- "When pageId is present, Forge patches the existing page and rewrites the canonical file."
3603
+ "When pageId is present, Forge patches the existing SQLite note record."
3602
3604
  ],
3603
3605
  example: '{"title":"Taste map","contentMarkdown":"# Taste map\\n\\n[[forge:goal:goal_123|Core goal]] influences this page.","spaceId":"wiki_space_shared"}'
3604
3606
  },
@@ -3609,19 +3611,19 @@ const AGENT_ONBOARDING_TOOL_INPUT_CATALOG = [
3609
3611
  inputShape: "{ spaceId?: string }",
3610
3612
  requiredFields: [],
3611
3613
  notes: [
3612
- "This is the explicit health surface for the file-first wiki vault.",
3614
+ "This is the explicit health surface for the SQLite-backed wiki memory layer.",
3613
3615
  "Use it before proposing cleanup work or auto-maintenance."
3614
3616
  ],
3615
3617
  example: '{"spaceId":"wiki_space_shared"}'
3616
3618
  },
3617
3619
  {
3618
3620
  toolName: "forge_sync_wiki_vault",
3619
- summary: "Resync Markdown files from the local wiki vault into Forge metadata.",
3620
- whenToUse: "Use after out-of-band file edits or imported file changes that should be reflected back in Forge.",
3621
+ summary: "Rebuild SQLite wiki search, link, and metadata indexes.",
3622
+ whenToUse: "Use after large SQLite wiki changes or maintenance work that should refresh derived metadata.",
3621
3623
  inputShape: "{ spaceId?: string }",
3622
3624
  requiredFields: [],
3623
3625
  notes: [
3624
- "Forge treats the vault as a first-class local artifact, so this route is the bridge back into app metadata."
3626
+ "Forge treats SQLite as the canonical wiki store; this route refreshes derived indexes."
3625
3627
  ],
3626
3628
  example: '{"spaceId":"wiki_space_shared"}'
3627
3629
  },
@@ -3640,11 +3642,11 @@ const AGENT_ONBOARDING_TOOL_INPUT_CATALOG = [
3640
3642
  {
3641
3643
  toolName: "forge_ingest_wiki_source",
3642
3644
  summary: "Ingest raw text, local files, or URLs into the wiki, preserving a raw source artifact and returning page plus proposal outputs.",
3643
- whenToUse: "Use when the operator wants source material compiled into file-first wiki memory and optional Forge-entity proposals.",
3645
+ whenToUse: "Use when the operator wants source material compiled into SQLite-backed wiki memory and optional Forge-entity proposals.",
3644
3646
  inputShape: '{ spaceId?: string, titleHint?: string, sourceKind: "raw_text"|"local_path"|"url", sourceText?: string, sourcePath?: string, sourceUrl?: string, mimeType?: string, llmProfileId?: string, parseStrategy?: "auto"|"text_only"|"multimodal", entityProposalMode?: "none"|"suggest", createAsKind?: "wiki"|"evidence", linkedEntityHints?: Array<{ entityType, entityId, anchorKey? }> }',
3645
3647
  requiredFields: ["sourceKind", "sourceText/sourcePath/sourceUrl"],
3646
3648
  notes: [
3647
- "Forge preserves a raw artifact under the wiki space's raw directory.",
3649
+ "Forge preserves a raw ingest artifact separately from SQLite page content.",
3648
3650
  "Entity proposals are suggestions only; they are not auto-applied."
3649
3651
  ],
3650
3652
  example: '{"sourceKind":"url","sourceUrl":"https://example.com/article","titleHint":"Research import","parseStrategy":"auto","entityProposalMode":"suggest"}'
@@ -4017,7 +4019,7 @@ function buildAgentOnboardingPayload(request) {
4017
4019
  task: "A concrete actionable work item. Task status is board state, not proof of live work.",
4018
4020
  taskRun: "A live work session attached to a task. Start, heartbeat, focus, complete, and release runs instead of faking work with status alone.",
4019
4021
  note: "A Markdown work note that can link to one or many entities. Use notes for progress evidence, context, and close-out summaries.",
4020
- wiki: "Forge Wiki is the file-first memory layer: local Markdown pages plus media, backlinks, optional embeddings, explicit spaces, and structured links back to Forge entities.",
4022
+ wiki: "Forge Wiki is the SQLite-backed memory layer: Markdown content in notes rows plus media, backlinks, optional embeddings, explicit spaces, and structured links back to Forge entities.",
4021
4023
  sleepSession: "A sleep session is a first-class health record with timing, sleep and bed duration, stage breakdown, recovery metrics, annotations, and Forge links back to planning or Psyche context.",
4022
4024
  workoutSession: "A workout session is a first-class sports record imported from HealthKit or generated from a habit. It holds workout type, timing, energy or distance when available, subjective effort, narrative context, and Forge links.",
4023
4025
  preferences: "Forge Preferences is the explicit taste-modeling domain. It has workspaces, contexts, concept libraries, direct items, pairwise judgments, direct signals, and inferred scores.",
@@ -4379,17 +4381,26 @@ function buildAgentOnboardingPayload(request) {
4379
4381
  movementTimeline: "/api/v1/movement/timeline",
4380
4382
  movementAllTime: "/api/v1/movement/all-time",
4381
4383
  movementPlaces: "/api/v1/movement/places",
4384
+ movementBoxDetail: "/api/v1/movement/boxes/:id",
4385
+ movementSettings: "/api/v1/movement/settings",
4386
+ movementSettingsUpdate: "/api/v1/movement/settings",
4382
4387
  movementTripDetail: "/api/v1/movement/trips/:id",
4383
4388
  movementSelection: "/api/v1/movement/selection",
4384
4389
  movementUserBoxPreflight: "/api/v1/movement/user-boxes/preflight",
4385
4390
  movementUserBoxUpdate: "/api/v1/movement/user-boxes/:id",
4391
+ movementUserBoxDelete: "/api/v1/movement/user-boxes/:id",
4386
4392
  movementAutomaticBoxInvalidate: "/api/v1/movement/automatic-boxes/:id/invalidate",
4387
4393
  movementStayUpdate: "/api/v1/movement/stays/:id",
4394
+ movementStayDelete: "/api/v1/movement/stays/:id",
4388
4395
  movementTripUpdate: "/api/v1/movement/trips/:id",
4396
+ movementTripDelete: "/api/v1/movement/trips/:id",
4389
4397
  movementTripPointUpdate: "/api/v1/movement/trips/:id/points/:pointId",
4398
+ movementTripPointDelete: "/api/v1/movement/trips/:id/points/:pointId",
4399
+ workbenchBoxCatalog: "/api/v1/workbench/catalog/boxes",
4390
4400
  workbenchFlows: "/api/v1/workbench/flows",
4391
4401
  workbenchFlowBySlug: "/api/v1/workbench/flows/by-slug/:slug",
4392
4402
  workbenchPublishedOutput: "/api/v1/workbench/flows/:id/output",
4403
+ workbenchRuns: "/api/v1/workbench/flows/:id/runs",
4393
4404
  workbenchRunDetail: "/api/v1/workbench/flows/:id/runs/:runId",
4394
4405
  workbenchNodeResult: "/api/v1/workbench/flows/:id/runs/:runId/nodes/:nodeId",
4395
4406
  workbenchLatestNodeOutput: "/api/v1/workbench/flows/:id/nodes/:nodeId/output",
@@ -4465,9 +4476,9 @@ function buildAgentOnboardingPayload(request) {
4465
4476
  saveSuggestionPlacement: "end_of_message",
4466
4477
  saveSuggestionTone: "gentle_optional",
4467
4478
  maxQuestionsPerTurn: 1,
4468
- psycheExplorationRule: "When a Psyche entity needs understanding first, begin with one exploratory question before any working formulation, replacement belief, suggested title, or save pitch. Keep the opening reflection to one or two short sentences, stay in plain prose instead of bullets or numbered lists, keep that first reply short, do not mention Forge search or save structure yet, avoid colons or list-shaped phrasing, prefer what/when/how over why until the experience is grounded, wait for the user's answer before offering a fuller formulation, ask permission before moving from charged exploration into naming or challenge when needed, make the next question help the user feel more able to name the experience rather than more examined, do not widen into adjacent entities until the current one has a working sentence the user recognizes, and once the lived experience is coherent stop deepening and help the user name it cleanly. When the user is updating a Psyche record because of one fresh episode, anchor in that episode before renaming the durable formulation. If the user accepts the wording, move toward the save instead of reopening deeper exploration.",
4479
+ psycheExplorationRule: "When a Psyche entity needs understanding first, begin with one exploratory question before any working formulation, replacement belief, suggested title, or save pitch. Keep the opening reflection to one or two short sentences, stay in plain prose instead of bullets or numbered lists, keep that first reply short, do not mention Forge search or save structure yet, avoid colons or list-shaped phrasing, prefer what/when/how over why until the experience is grounded, wait for the user's answer before offering a fuller formulation, ask permission before moving from charged exploration into naming or challenge when needed, make the next question help the user feel more able to name the experience rather than more examined, do not widen into adjacent entities until the current one has a working sentence the user recognizes, and once the lived experience is coherent stop deepening and help the user name it cleanly. When the user is updating a Psyche record because of one fresh episode, anchor in that episode before renaming the durable formulation, begin with the smallest part of the old wording that no longer fits, and do not reopen the full origin story unless the new understanding is truly structural. If the user accepts the wording, move toward the save instead of reopening deeper exploration.",
4469
4480
  specializedSurfaceRule: "For Movement, Life Force, and Workbench, clarify the lane first, then name the dedicated route family in plain language and do not guess at a generic CRUD path. Use specializedDomainSurfaces.routeSelectionQuestions when they are present so the next follow-up stays route-selective instead of generic. Once the lane is clear, talk in route-relevant nouns such as timeline, overlay, weekday template, published output, run detail, or node result rather than generic record language. If the truth of the current state is still uncertain, read the relevant specialized view before you mutate it. When the user already named a precise correction or review target, confirm only the route-selecting detail that is still missing. After a concrete specialized-surface correction, read the relevant specialized view back when the user is trying to understand the result rather than just store it. The canonical runtime routes stay under /api/v1/*, and the OpenClaw HTTP mirror exposes the same families under /forge/v1/movement, /forge/v1/life-force, and /forge/v1/workbench.",
4470
- reviewShortcutRule: "When the user is reviewing or correcting an existing record, narrow the saved object, timeframe, or route family first. Do not reopen the whole intake unless the user is actually redefining the record.",
4481
+ reviewShortcutRule: "When the user is reviewing or correcting an existing record, ask what practical question they want the read or correction to answer, then narrow the saved object, timeframe, or route family first. Do not reopen the whole intake unless the user is actually redefining the record.",
4471
4482
  readModelWriteRule: "Self-observation is note-backed and should be written through observed notes with frontmatter.observedAt. Sleep and workout sessions stay on batch CRUD by default; use the reflective review helpers only when enriching one already-known record after review.",
4472
4483
  psycheOpeningQuestionRule: "Prefer a concrete opening question tied to the entity: ask when the value mattered, what happened the last time the pattern appeared, what cue or body signal came first before the behavior, what the belief starts saying about self or outcome, what feels most at risk inside the mode, what the part is trying to get the user to do or stop doing, or where the shift began in the incident. Reflect briefly before the question, choose one follow-up lane at a time, say what is becoming clearer before the next deeper question, and if several Psyche entities are visible hold the adjacent ones lightly until the main container is clear.",
4473
4484
  duplicateCheckRoute: "/api/v1/entities/search",
@@ -34,14 +34,12 @@ function resolveLegacyDatabasePath(root = dataRoot) {
34
34
  function hasCanonicalRuntimeLayout(root = dataRoot) {
35
35
  const canonicalRoot = resolveCanonicalDataDir(root);
36
36
  return (existsSync(resolveCanonicalDatabasePath(root)) ||
37
- existsSync(path.join(canonicalRoot, "wiki")) ||
38
37
  existsSync(path.join(canonicalRoot, "wiki-ingest")) ||
39
38
  existsSync(path.join(canonicalRoot, ".forge-secrets.key")));
40
39
  }
41
40
  function hasLegacyRuntimeLayout(root = dataRoot) {
42
41
  const legacyRoot = resolveLegacyDataDir(root);
43
42
  return (existsSync(resolveLegacyDatabasePath(root)) ||
44
- existsSync(path.join(legacyRoot, "wiki")) ||
45
43
  existsSync(path.join(legacyRoot, "wiki-ingest")) ||
46
44
  existsSync(path.join(legacyRoot, ".forge-secrets.key")));
47
45
  }
@@ -81,6 +79,7 @@ export function resolveDefaultDataRoot(currentWorkingDir = process.cwd()) {
81
79
  }
82
80
  let dataRoot = resolveDefaultDataRoot();
83
81
  let seedDemoDataEnabled = false;
82
+ let legacyWikiAutoImportEnabled = true;
84
83
  let db = null;
85
84
  let transactionDepth = 0;
86
85
  let savepointCounter = 0;
@@ -163,6 +162,9 @@ export function configureDatabase(options = {}) {
163
162
  seedDemoDataEnabled = options.seedDemoData;
164
163
  }
165
164
  }
165
+ export function configureLegacyWikiAutoImport(enabled) {
166
+ legacyWikiAutoImportEnabled = enabled;
167
+ }
166
168
  async function listMigrationFiles() {
167
169
  const files = await readdir(migrationsDir);
168
170
  return files.filter((file) => file.endsWith(".sql")).sort();
@@ -391,6 +393,13 @@ export async function initializeDatabase() {
391
393
  seedData();
392
394
  }
393
395
  ensureQuestionnaireSeeds();
396
+ if (legacyWikiAutoImportEnabled) {
397
+ const { importLegacyWikiMarkdownOnStartup } = await import("./services/legacy-wiki-markdown-import.js");
398
+ const legacyWikiImport = await importLegacyWikiMarkdownOnStartup(getDataDir());
399
+ if (legacyWikiImport.scanned > 0) {
400
+ logForgeDebug(`[forge-db] imported legacy wiki markdown scanned=${legacyWikiImport.scanned} inserted=${legacyWikiImport.inserted} updated=${legacyWikiImport.updated} backed_up=${legacyWikiImport.backedUp} backup_path=${legacyWikiImport.backupPath}`);
401
+ }
402
+ }
394
403
  }
395
404
  export function configureDatabaseSeeding(enabled) {
396
405
  seedDemoDataEnabled = enabled;
@@ -115,7 +115,7 @@ const API_TAGS = [
115
115
  },
116
116
  {
117
117
  name: "Wiki",
118
- description: "File-first wiki settings, pages, ingest, sync, health, and search."
118
+ description: "SQLite-backed wiki settings, pages, ingest, sync, health, and search."
119
119
  },
120
120
  {
121
121
  name: "Preferences",
@@ -3253,17 +3253,26 @@ export function buildOpenApiDocument() {
3253
3253
  "movementTimeline",
3254
3254
  "movementAllTime",
3255
3255
  "movementPlaces",
3256
+ "movementBoxDetail",
3257
+ "movementSettings",
3258
+ "movementSettingsUpdate",
3256
3259
  "movementTripDetail",
3257
3260
  "movementSelection",
3258
3261
  "movementUserBoxPreflight",
3259
3262
  "movementUserBoxUpdate",
3263
+ "movementUserBoxDelete",
3260
3264
  "movementAutomaticBoxInvalidate",
3261
3265
  "movementStayUpdate",
3266
+ "movementStayDelete",
3262
3267
  "movementTripUpdate",
3268
+ "movementTripDelete",
3263
3269
  "movementTripPointUpdate",
3270
+ "movementTripPointDelete",
3271
+ "workbenchBoxCatalog",
3264
3272
  "workbenchFlows",
3265
3273
  "workbenchFlowBySlug",
3266
3274
  "workbenchPublishedOutput",
3275
+ "workbenchRuns",
3267
3276
  "workbenchRunDetail",
3268
3277
  "workbenchNodeResult",
3269
3278
  "workbenchLatestNodeOutput",
@@ -3292,17 +3301,26 @@ export function buildOpenApiDocument() {
3292
3301
  movementTimeline: { type: "string" },
3293
3302
  movementAllTime: { type: "string" },
3294
3303
  movementPlaces: { type: "string" },
3304
+ movementBoxDetail: { type: "string" },
3305
+ movementSettings: { type: "string" },
3306
+ movementSettingsUpdate: { type: "string" },
3295
3307
  movementTripDetail: { type: "string" },
3296
3308
  movementSelection: { type: "string" },
3297
3309
  movementUserBoxPreflight: { type: "string" },
3298
3310
  movementUserBoxUpdate: { type: "string" },
3311
+ movementUserBoxDelete: { type: "string" },
3299
3312
  movementAutomaticBoxInvalidate: { type: "string" },
3300
3313
  movementStayUpdate: { type: "string" },
3314
+ movementStayDelete: { type: "string" },
3301
3315
  movementTripUpdate: { type: "string" },
3316
+ movementTripDelete: { type: "string" },
3302
3317
  movementTripPointUpdate: { type: "string" },
3318
+ movementTripPointDelete: { type: "string" },
3319
+ workbenchBoxCatalog: { type: "string" },
3303
3320
  workbenchFlows: { type: "string" },
3304
3321
  workbenchFlowBySlug: { type: "string" },
3305
3322
  workbenchPublishedOutput: { type: "string" },
3323
+ workbenchRuns: { type: "string" },
3306
3324
  workbenchRunDetail: { type: "string" },
3307
3325
  workbenchNodeResult: { type: "string" },
3308
3326
  workbenchLatestNodeOutput: { type: "string" },
@@ -4830,6 +4848,24 @@ export function buildOpenApiDocument() {
4830
4848
  }
4831
4849
  }
4832
4850
  },
4851
+ "/api/v1/movement/boxes/{id}": {
4852
+ get: {
4853
+ summary: "Read one movement box with projected detail, provenance, and raw linked evidence",
4854
+ responses: {
4855
+ "200": jsonResponse({
4856
+ type: "object",
4857
+ required: ["movement"],
4858
+ properties: {
4859
+ movement: {
4860
+ type: "object",
4861
+ additionalProperties: true
4862
+ }
4863
+ }
4864
+ }, "Movement box detail"),
4865
+ "404": { $ref: "#/components/responses/Error" }
4866
+ }
4867
+ }
4868
+ },
4833
4869
  "/api/v1/movement/trips/{id}": {
4834
4870
  get: {
4835
4871
  summary: "Read one movement trip with its full detail",
@@ -5260,7 +5296,7 @@ export function buildOpenApiDocument() {
5260
5296
  }
5261
5297
  },
5262
5298
  post: {
5263
- summary: "Create a wiki page through the file-backed wiki surface",
5299
+ summary: "Create a wiki page through the SQLite-backed wiki surface",
5264
5300
  responses: {
5265
5301
  "200": jsonResponse({
5266
5302
  type: "object",
@@ -5288,7 +5324,7 @@ export function buildOpenApiDocument() {
5288
5324
  }
5289
5325
  },
5290
5326
  patch: {
5291
- summary: "Update an existing wiki page through the file-backed surface",
5327
+ summary: "Update an existing wiki page through the SQLite-backed surface",
5292
5328
  responses: {
5293
5329
  "200": jsonResponse({
5294
5330
  type: "object",
@@ -5334,7 +5370,7 @@ export function buildOpenApiDocument() {
5334
5370
  },
5335
5371
  "/api/v1/wiki/sync": {
5336
5372
  post: {
5337
- summary: "Resync markdown files from the local wiki vault into Forge metadata",
5373
+ summary: "Rebuild SQLite wiki search, link, and metadata indexes",
5338
5374
  responses: {
5339
5375
  "200": jsonResponse({
5340
5376
  type: "object",
@@ -40,6 +40,9 @@ function normalizeTags(tags) {
40
40
  return true;
41
41
  });
42
42
  }
43
+ function canonicalNoteSourcePath() {
44
+ return "";
45
+ }
43
46
  function parseTagsJson(raw) {
44
47
  try {
45
48
  const parsed = JSON.parse(raw);
@@ -438,7 +441,7 @@ export function createNote(input, context) {
438
441
  source_path, frontmatter_json, revision_hash, last_synced_at, created_at, updated_at
439
442
  )
440
443
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
441
- .run(id, wikiFields.kind, wikiFields.title, wikiFields.slug, wikiFields.spaceId, wikiFields.parentSlug, wikiFields.indexOrder, wikiFields.showInIndex ? 1 : 0, JSON.stringify(wikiFields.aliases), wikiFields.summary, parsed.contentMarkdown, contentPlain, parsed.author ?? context.actor ?? null, context.source, JSON.stringify(parsed.tags), parsed.destroyAt, parsed.sourcePath, JSON.stringify(parsed.frontmatter), parsed.revisionHash, parsed.lastSyncedAt ?? null, now, now);
444
+ .run(id, wikiFields.kind, wikiFields.title, wikiFields.slug, wikiFields.spaceId, wikiFields.parentSlug, wikiFields.indexOrder, wikiFields.showInIndex ? 1 : 0, JSON.stringify(wikiFields.aliases), wikiFields.summary, parsed.contentMarkdown, contentPlain, parsed.author ?? context.actor ?? null, context.source, JSON.stringify(parsed.tags), parsed.destroyAt, canonicalNoteSourcePath(), JSON.stringify(parsed.frontmatter), parsed.revisionHash, parsed.lastSyncedAt ?? null, now, now);
442
445
  insertLinks(id, parsed.links, now);
443
446
  setEntityOwner("note", id, parsed.userId, parsed.author ?? context.actor ?? null);
444
447
  clearDeletedEntityRecord("note", id);
@@ -504,7 +507,7 @@ export function updateNote(noteId, input, context) {
504
507
  existing
505
508
  });
506
509
  const nextFrontmatter = patch.frontmatter === undefined ? existing.frontmatter : patch.frontmatter;
507
- const nextSourcePath = patch.sourcePath === undefined ? existing.sourcePath : patch.sourcePath;
510
+ const nextSourcePath = canonicalNoteSourcePath();
508
511
  const nextRevisionHash = patch.revisionHash === undefined
509
512
  ? existing.revisionHash
510
513
  : patch.revisionHash;