diff --git a/apps/cli/composition.md b/apps/cli/composition.md
index 9c0d5b5f06..0e2af8b863 100644
--- a/apps/cli/composition.md
+++ b/apps/cli/composition.md
@@ -96,8 +96,36 @@ flowchart LR
cfg --> plugin_tui_fs_local
plugin_tui_llm_deepseek["llm-deepseek
@deepseek-ai/dsh-llm-deepseek"]
cfg --> plugin_tui_llm_deepseek
- plugin_tui_tools["tools
@deepseek-ai/dsh-tool-ask-user"]
- cfg --> plugin_tui_tools
+ plugin_tui_hmr["hmr
@cordisjs/plugin-hmr"]
+ cfg --> plugin_tui_hmr
+ plugin_tui_invariants["invariants
@deepseek-ai/dsh-invariants"]
+ cfg --> plugin_tui_invariants
+ plugin_tui_session_invariant["session-invariant
@deepseek-ai/dsh-session/invariant"]
+ cfg --> plugin_tui_session_invariant
+ plugin_tui_agent_invariant["agent-invariant
@deepseek-ai/dsh-agent/invariant"]
+ cfg --> plugin_tui_agent_invariant
+ plugin_tui_scope_invariant["scope-invariant
@deepseek-ai/dsh-scope/invariant"]
+ cfg --> plugin_tui_scope_invariant
+ plugin_tui_agent_loop_invariant["agent-loop-invariant
@deepseek-ai/dsh-agent-loop/invariant"]
+ cfg --> plugin_tui_agent_loop_invariant
+ plugin_tui_session_checkpoint_policy["session-checkpoint-policy
@deepseek-ai/dsh-session-checkpoint-policy"]
+ cfg --> plugin_tui_session_checkpoint_policy
+ plugin_tui_session_query_sqlite["session-query-sqlite
@deepseek-ai/dsh-session-query-sqlite"]
+ cfg --> plugin_tui_session_query_sqlite
+ plugin_tui_session_reference["session-reference
@deepseek-ai/dsh-session-reference"]
+ cfg --> plugin_tui_session_reference
+ plugin_tui_tool_result_prune["tool-result-prune
@deepseek-ai/dsh-compact-tool-result-prune"]
+ cfg --> plugin_tui_tool_result_prune
+ plugin_tui_tool_goal["tool-goal
@deepseek-ai/dsh-tool-goal"]
+ cfg --> plugin_tui_tool_goal
+ plugin_tui_tool_ralph["tool-ralph
@deepseek-ai/dsh-tool-ralph"]
+ cfg --> plugin_tui_tool_ralph
+ plugin_tui_tui_prompt["tui-prompt
@deepseek-ai/dsh-tui/prompt"]
+ cfg --> plugin_tui_tui_prompt
+ plugin_tui_tui["tui
@deepseek-ai/dsh-tui"]
+ cfg --> plugin_tui_tui
+ plugin_tui_tool_ask_user["tool-ask-user
@deepseek-ai/dsh-tool-ask-user"]
+ cfg --> plugin_tui_tool_ask_user
```
| Plugin id | Package / module |
@@ -146,7 +174,21 @@ flowchart LR
| `agent-loop` | `@deepseek-ai/dsh-agent-loop` |
| `fs-local` | `@deepseek-ai/dsh-fs-local` |
| `llm-deepseek` | `@deepseek-ai/dsh-llm-deepseek` |
-| `tools` | `@deepseek-ai/dsh-tool-ask-user` |
+| `hmr` | `@cordisjs/plugin-hmr` |
+| `invariants` | `@deepseek-ai/dsh-invariants` |
+| `session-invariant` | `@deepseek-ai/dsh-session/invariant` |
+| `agent-invariant` | `@deepseek-ai/dsh-agent/invariant` |
+| `scope-invariant` | `@deepseek-ai/dsh-scope/invariant` |
+| `agent-loop-invariant` | `@deepseek-ai/dsh-agent-loop/invariant` |
+| `session-checkpoint-policy` | `@deepseek-ai/dsh-session-checkpoint-policy` |
+| `session-query-sqlite` | `@deepseek-ai/dsh-session-query-sqlite` |
+| `session-reference` | `@deepseek-ai/dsh-session-reference` |
+| `tool-result-prune` | `@deepseek-ai/dsh-compact-tool-result-prune` |
+| `tool-goal` | `@deepseek-ai/dsh-tool-goal` |
+| `tool-ralph` | `@deepseek-ai/dsh-tool-ralph` |
+| `tui-prompt` | `@deepseek-ai/dsh-tui/prompt` |
+| `tui` | `@deepseek-ai/dsh-tui` |
+| `tool-ask-user` | `@deepseek-ai/dsh-tool-ask-user` |
Source configs: [`apps/cli/base.cordis.yml`](base.cordis.yml), [`apps/cli/tui.cordis.yml`](tui.cordis.yml).
diff --git a/scripts/gen-doc-graphs.ts b/scripts/gen-doc-graphs.ts
index 54cdc11a50..642f97db66 100644
--- a/scripts/gen-doc-graphs.ts
+++ b/scripts/gen-doc-graphs.ts
@@ -8,6 +8,7 @@
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
import { dirname, relative, resolve } from 'node:path'
import ts from 'typescript'
+import { DEFAULT_SCHEMA, load, Type } from 'js-yaml'
import { collectEvents, collectServices } from './gen-cordis-catalog.ts'
import {
collectPackageGraph,
@@ -566,29 +567,28 @@ function renderCapabilitySeams(pkgs: Pkg[]): string {
return lines.join('\n')
}
-function parseExampleCordis(rel: string): ExamplePlugin[] {
- const text = readFileSync(resolve(root, rel), 'utf8')
- const plugins: ExamplePlugin[] = []
- let current: { id: string; name?: string } | null = null
- const flush = (): void => {
- if (current?.name) plugins.push({ id: current.id, name: current.name })
- }
- for (const line of text.split('\n')) {
- const id = /^-\s+id:\s+(.+?)\s*$/.exec(line)
- if (id?.[1] !== undefined) {
- flush()
- current = { id: stripYamlScalar(id[1]) }
- continue
- }
- const name = /^\s+name:\s+(.+?)\s*$/.exec(line)
- if (name?.[1] !== undefined && current) current.name = stripYamlScalar(name[1])
- }
- flush()
- return plugins
-}
+const jsExpressionType = new Type('tag:yaml.org,2002:js', {
+ kind: 'scalar',
+ construct: (value: string | null): string => value ?? '',
+})
+const cordisSchema = DEFAULT_SCHEMA.extend([jsExpressionType])
-function stripYamlScalar(value: string): string {
- return value.trim().replace(/^['"]|['"]$/g, '')
+function parseExampleCordis(rel: string): ExamplePlugin[] {
+ const document = load(readFileSync(resolve(root, rel), 'utf8'), { schema: cordisSchema })
+ if (!Array.isArray(document)) throw new Error(`${rel}: expected a top-level config array`)
+ const plugins: ExamplePlugin[] = []
+ const visit = (entries: unknown[]): void => {
+ for (const value of entries) {
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) continue
+ const entry = value as { id?: unknown; name?: unknown; insert?: unknown }
+ if (typeof entry.id === 'string' && typeof entry.name === 'string') {
+ plugins.push({ id: entry.id, name: entry.name })
+ }
+ if (Array.isArray(entry.insert)) visit(entry.insert)
+ }
+ }
+ visit(document)
+ return plugins
}
const APP_EXAMPLES = [