@deftai/directive-core 0.91.0 → 0.93.0

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.
Files changed (92) hide show
  1. package/dist/cache/fetch.js +3 -2
  2. package/dist/cache/io.d.ts +13 -2
  3. package/dist/cache/io.js +36 -7
  4. package/dist/cache/operations.js +5 -3
  5. package/dist/doctor/index.d.ts +1 -0
  6. package/dist/doctor/index.js +1 -0
  7. package/dist/doctor/main.js +49 -0
  8. package/dist/doctor/openclaw-l2-adapter.d.ts +26 -0
  9. package/dist/doctor/openclaw-l2-adapter.js +199 -0
  10. package/dist/hooks/dispatcher.d.ts +6 -1
  11. package/dist/hooks/dispatcher.js +30 -0
  12. package/dist/index.d.ts +2 -0
  13. package/dist/index.js +2 -0
  14. package/dist/init-deposit/gitignore.js +3 -0
  15. package/dist/init-deposit/hygiene.d.ts +43 -1
  16. package/dist/init-deposit/hygiene.js +134 -10
  17. package/dist/init-deposit/index.d.ts +3 -0
  18. package/dist/init-deposit/index.js +3 -0
  19. package/dist/init-deposit/init-deposit.js +10 -0
  20. package/dist/init-deposit/refresh.js +11 -0
  21. package/dist/init-deposit/scaffold.js +6 -134
  22. package/dist/init-deposit/skill-discovery-deposit.d.ts +65 -0
  23. package/dist/init-deposit/skill-discovery-deposit.js +287 -0
  24. package/dist/init-deposit/skill-discovery-hosts.d.ts +94 -0
  25. package/dist/init-deposit/skill-discovery-hosts.js +217 -0
  26. package/dist/init-deposit/slash-deposit.d.ts +46 -0
  27. package/dist/init-deposit/slash-deposit.js +174 -0
  28. package/dist/orchestration/probe-session.d.ts +5 -1
  29. package/dist/orchestration/probe-session.js +24 -13
  30. package/dist/platform/agents-md.js +1 -1
  31. package/dist/policy/deft-directive-disable.d.ts +86 -0
  32. package/dist/policy/deft-directive-disable.js +167 -0
  33. package/dist/policy/delivery-branch.d.ts +33 -0
  34. package/dist/policy/delivery-branch.js +124 -0
  35. package/dist/policy/host-slash-commands.d.ts +28 -0
  36. package/dist/policy/host-slash-commands.js +103 -0
  37. package/dist/policy/index.d.ts +3 -0
  38. package/dist/policy/index.js +61 -5
  39. package/dist/scope/brief-io.d.ts +3 -1
  40. package/dist/scope/brief-io.js +5 -3
  41. package/dist/scope/delivery-evidence.d.ts +112 -0
  42. package/dist/scope/delivery-evidence.js +419 -0
  43. package/dist/scope/index.d.ts +1 -0
  44. package/dist/scope/index.js +1 -0
  45. package/dist/scope/main.d.ts +5 -0
  46. package/dist/scope/main.js +98 -3
  47. package/dist/scope/registry-artifact-sync.js +4 -1
  48. package/dist/scope/transition.d.ts +11 -1
  49. package/dist/scope/transition.js +30 -4
  50. package/dist/session/ritual-sentinel.js +21 -12
  51. package/dist/session/session-start-hook.d.ts +3 -0
  52. package/dist/session/session-start-hook.js +21 -0
  53. package/dist/session/session-start.js +31 -0
  54. package/dist/slash/emitters.d.ts +102 -0
  55. package/dist/slash/emitters.js +148 -0
  56. package/dist/slash/generator.d.ts +98 -0
  57. package/dist/slash/generator.js +145 -0
  58. package/dist/slash/index.d.ts +16 -0
  59. package/dist/slash/index.js +16 -0
  60. package/dist/slash/openclaw-adapter.d.ts +64 -0
  61. package/dist/slash/openclaw-adapter.js +198 -0
  62. package/dist/slash/openclaw-deposit.d.ts +73 -0
  63. package/dist/slash/openclaw-deposit.js +279 -0
  64. package/dist/slash/openclaw-slugs.d.ts +52 -0
  65. package/dist/slash/openclaw-slugs.js +126 -0
  66. package/dist/slash/product-set.d.ts +50 -0
  67. package/dist/slash/product-set.js +142 -0
  68. package/dist/swarm/complete-cohort.d.ts +20 -1
  69. package/dist/swarm/complete-cohort.js +56 -9
  70. package/dist/swarm/finalize-cohort-cli.js +9 -0
  71. package/dist/swarm/finalize-cohort.d.ts +8 -0
  72. package/dist/swarm/finalize-cohort.js +217 -21
  73. package/dist/user-config/experimental-rules.d.ts +43 -0
  74. package/dist/user-config/experimental-rules.js +162 -0
  75. package/dist/user-config/index.d.ts +1 -0
  76. package/dist/user-config/index.js +1 -0
  77. package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
  78. package/dist/vbrief-validate/plan-hooks.js +50 -0
  79. package/dist/verify-source/contained-writes.js +1 -1
  80. package/dist/xbrief/create.d.ts +36 -0
  81. package/dist/xbrief/create.js +285 -0
  82. package/dist/xbrief/index.d.ts +14 -0
  83. package/dist/xbrief/index.js +42 -0
  84. package/dist/xbrief/paths.d.ts +37 -0
  85. package/dist/xbrief/paths.js +123 -0
  86. package/dist/xbrief/styles.d.ts +36 -0
  87. package/dist/xbrief/styles.js +235 -0
  88. package/dist/xbrief/types.d.ts +50 -0
  89. package/dist/xbrief/types.js +17 -0
  90. package/dist/xbrief/verify.d.ts +30 -0
  91. package/dist/xbrief/verify.js +251 -0
  92. package/package.json +11 -3
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Host-agnostic thin-wrapper generator for product slash commands (#3052 / epic #55).
3
+ *
4
+ * Emits IR + markdown templates that per-host emitters (#3053) format into native
5
+ * command/prompt/workflow files. Wrappers stay thin (L5): frontmatter description +
6
+ * short dispatch pointer — never inlined strategy/skill/commands.md bodies.
7
+ *
8
+ * ## Token / context budgets (from #55 token design rules)
9
+ *
10
+ * | When | Target |
11
+ * |---|---|
12
+ * | Idle (user never invokes `/deft…`) | ~0 from command files |
13
+ * | Catalog (`/` menu: name + description × N) | ≤ ~1k tok for the product set |
14
+ * | Single invoke | ~40–100 tok thin wrapper body |
15
+ * | After dispatch | cost of the target strategy/skill (unchanged) |
16
+ *
17
+ * Multi-host deposit does not multiply tokens in one session: each host reads only
18
+ * its own command directory. Real spend is the loaded artifact after invoke.
19
+ */
20
+ import { listProductCommands, logicalIdToFilename, logicalIdToFilenameStem, PRODUCT_COMMAND_COUNT, } from "./product-set.js";
21
+ /** Rough UTF-8 bytes-per-token estimate (aligned with agents-md-budget). */
22
+ export const BYTES_PER_TOKEN_ESTIMATE = 4;
23
+ /** L5: invoke wrapper body budget (≈100 tokens). */
24
+ export const MAX_WRAPPER_BODY_TOKENS = 100;
25
+ /** Per-command catalog description hard cap for tests (order-of-magnitude 20–50). */
26
+ export const MAX_DESCRIPTION_TOKENS = 50;
27
+ /** Full product-set description catalog budget (≤ ~1k tok). */
28
+ export const MAX_CATALOG_TOKENS = 1000;
29
+ /** Estimate tokens from a UTF-8 string (bytes / 4). */
30
+ export function estimateTokens(text) {
31
+ return Math.ceil(Buffer.byteLength(text, "utf8") / BYTES_PER_TOKEN_ESTIMATE);
32
+ }
33
+ /**
34
+ * Render the thin body only: short dispatch pointer, no inlined target content.
35
+ *
36
+ * Keeps invoke cost in the ~40–100 token band (L5).
37
+ */
38
+ export function renderThinWrapperBody(command) {
39
+ const lines = [
40
+ `Read and follow \`${command.dispatchPath}\` (content-relative; deposit under \`.deft/core/\` when installed).`,
41
+ "Honor `$ARGUMENTS` as documented for this command.",
42
+ "Do not inline the strategy, skill, or commands.md body here.",
43
+ ];
44
+ return `${lines.join("\n")}\n`;
45
+ }
46
+ /**
47
+ * Render host-agnostic file markdown: description frontmatter + thin body.
48
+ *
49
+ * Per-host emitters may re-shape frontmatter keys; the body semantics stay shared.
50
+ */
51
+ export function renderThinWrapperFile(command) {
52
+ const body = renderThinWrapperBody(command);
53
+ const frontmatterLines = [`description: ${yamlSingleLine(command.description)}`];
54
+ if (command.argumentHint !== undefined) {
55
+ frontmatterLines.push(`argument-hint: ${yamlSingleLine(command.argumentHint)}`);
56
+ }
57
+ return `---\n${frontmatterLines.join("\n")}\n---\n\n${body}`;
58
+ }
59
+ function yamlSingleLine(value) {
60
+ // Descriptions are product-controlled short ASCII; quote when needed for YAML safety.
61
+ if (/[:#{}[\],&*!|>'"%@`]|^\s|\s$/.test(value) || value === "") {
62
+ return JSON.stringify(value);
63
+ }
64
+ return value;
65
+ }
66
+ /** Build {@link ThinWrapperIR} for one product command. */
67
+ export function generateThinWrapper(command) {
68
+ const bodyMarkdown = renderThinWrapperBody(command);
69
+ const fileMarkdown = renderThinWrapperFile(command);
70
+ return {
71
+ logicalId: command.logicalId,
72
+ filenameStem: command.filenameStem,
73
+ filename: logicalIdToFilename(command.logicalId),
74
+ description: command.description,
75
+ dispatchKind: command.dispatchKind,
76
+ dispatchPath: command.dispatchPath,
77
+ argumentHint: command.argumentHint,
78
+ bodyMarkdown,
79
+ fileMarkdown,
80
+ estimatedBodyTokens: estimateTokens(bodyMarkdown),
81
+ estimatedDescriptionTokens: estimateTokens(command.description),
82
+ };
83
+ }
84
+ /**
85
+ * Generate thin wrappers for the full L2 product set (stable order, count === 13).
86
+ *
87
+ * This is the primary API for #3053 emitters.
88
+ */
89
+ export function generateThinWrappers(commands = listProductCommands()) {
90
+ return commands.map(generateThinWrapper);
91
+ }
92
+ /** Measure catalog + per-wrapper body budgets for the generated set. */
93
+ export function measureTokenBudget(wrappers = generateThinWrappers()) {
94
+ const catalogTokens = wrappers.reduce((sum, w) => sum + w.estimatedDescriptionTokens, 0);
95
+ const maxBodyTokens = wrappers.reduce((max, w) => Math.max(max, w.estimatedBodyTokens), 0);
96
+ const maxDescriptionTokens = wrappers.reduce((max, w) => Math.max(max, w.estimatedDescriptionTokens), 0);
97
+ const withinBodyBudget = wrappers.every((w) => w.estimatedBodyTokens <= MAX_WRAPPER_BODY_TOKENS);
98
+ const withinDescriptionBudget = wrappers.every((w) => w.estimatedDescriptionTokens <= MAX_DESCRIPTION_TOKENS);
99
+ const withinCatalogBudget = catalogTokens <= MAX_CATALOG_TOKENS;
100
+ return {
101
+ commandCount: wrappers.length,
102
+ catalogTokens,
103
+ maxBodyTokens,
104
+ maxDescriptionTokens,
105
+ withinBodyBudget,
106
+ withinDescriptionBudget,
107
+ withinCatalogBudget,
108
+ ok: wrappers.length === PRODUCT_COMMAND_COUNT &&
109
+ withinBodyBudget &&
110
+ withinDescriptionBudget &&
111
+ withinCatalogBudget,
112
+ };
113
+ }
114
+ /**
115
+ * Structural check that a wrapper file looks like a thin pointer template.
116
+ * Used by unit tests and available to emitters for deposit validation.
117
+ */
118
+ export function isThinWrapperMarkdown(fileMarkdown, dispatchPath) {
119
+ if (!fileMarkdown.startsWith("---\n")) {
120
+ return false;
121
+ }
122
+ const close = fileMarkdown.indexOf("\n---\n", 4);
123
+ if (close < 0) {
124
+ return false;
125
+ }
126
+ const frontmatter = fileMarkdown.slice(4, close);
127
+ if (!/^description:\s.+/m.test(frontmatter)) {
128
+ return false;
129
+ }
130
+ const body = fileMarkdown.slice(close + 5);
131
+ if (!body.includes(dispatchPath)) {
132
+ return false;
133
+ }
134
+ // Fat-body guard: refuse multi-kilobyte dumps / obvious strategy section headers.
135
+ if (estimateTokens(body) > MAX_WRAPPER_BODY_TOKENS) {
136
+ return false;
137
+ }
138
+ if (/^##\s+(Phase|Workflow|Steps|Acceptance)\b/m.test(body)) {
139
+ return false;
140
+ }
141
+ return true;
142
+ }
143
+ /** Re-export mapping helpers for emitter convenience without a second import path. */
144
+ export { logicalIdToFilename, logicalIdToFilenameStem, PRODUCT_COMMAND_COUNT };
145
+ //# sourceMappingURL=generator.js.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Slash-command generator + per-host emitters (#3052 / #3053 / epic #55)
3
+ * + OpenClaw L2 product-command adapter (#3064).
4
+ *
5
+ * Product command table + thin-wrapper IR/templates + host path layouts.
6
+ * Disk deposit for file hosts: init-deposit `writeSlashCommandDeposit` (#3054).
7
+ * OpenClaw skills deposit: `depositOpenClawL2ProductCommands` (#3064) — not a
8
+ * fake `.openclaw/commands/` file emitter.
9
+ */
10
+ export { assertThinHostEmission, emitAllHostCommandFiles, emitHostCommandFiles, getHostCommandLayout, HOST_COMMAND_LAYOUTS, type HostCommandLayout, type HostEmittedFile, hostRelativePath, isSlashEmitterHostId, listSlashEmitterHosts, renderHostFileContents, SLASH_EMITTER_HOSTS, type SlashEmitterHostId, } from "./emitters.js";
11
+ export { BYTES_PER_TOKEN_ESTIMATE, estimateTokens, generateThinWrapper, generateThinWrappers, isThinWrapperMarkdown, MAX_CATALOG_TOKENS, MAX_DESCRIPTION_TOKENS, MAX_WRAPPER_BODY_TOKENS, measureTokenBudget, renderThinWrapperBody, renderThinWrapperFile, type ThinWrapperIR, type TokenBudgetReport, } from "./generator.js";
12
+ export { assertThinOpenClawArtifacts, generateOpenClawSkillArtifacts, isManagedOpenClawL2Skill, isManagedOpenClawRouterSkill, isThinOpenClawSkillMarkdown, listOpenClawManagedSkillSlugs, listOpenClawProductSkillSlugs, OPENCLAW_L2_MANAGED_MARKER, OPENCLAW_L2_ROUTER_MARKER, type OpenClawSkillArtifact, renderOpenClawProductSkillMarkdown, renderOpenClawRouterSkillMarkdown, } from "./openclaw-adapter.js";
13
+ export { DEFAULT_OPENCLAW_PRODUCT_COMMANDS, depositOpenClawL2ProductCommands, FIELD_OPENCLAW_PRODUCT_COMMANDS, FIELD_OPENCLAW_PRODUCT_COMMANDS_CLI_ALIAS, inspectOpenClawProductCommands, loadOpenClawProductCommandsPolicyFromProject, type OpenClawL2DepositOptions, type OpenClawL2DepositResult, type OpenClawProductCommandsPolicy, type OpenClawProductCommandsPolicyField, resolveOpenClawProductCommandsPolicy, validateOpenClawProductCommands, } from "./openclaw-deposit.js";
14
+ export { assertOpenClawSlugMapIntegrity, isValidOpenClawSlug, listOpenClawSlugEntries, logicalIdToOpenClawSlug, OPENCLAW_LOGICAL_ID_BY_SLUG, OPENCLAW_ROUTER_SLUG, OPENCLAW_SLUG_BY_LOGICAL_ID, OPENCLAW_SLUG_MAX_LEN, OPENCLAW_SLUG_PATTERN, type OpenClawSlugEntry, openClawSlugToLogicalId, } from "./openclaw-slugs.js";
15
+ export { getProductCommand, listProductCommands, logicalIdToFilename, logicalIdToFilenameStem, PRODUCT_COMMAND_COUNT, PRODUCT_COMMANDS, type ProductCommand, type SlashDispatchKind, } from "./product-set.js";
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Slash-command generator + per-host emitters (#3052 / #3053 / epic #55)
3
+ * + OpenClaw L2 product-command adapter (#3064).
4
+ *
5
+ * Product command table + thin-wrapper IR/templates + host path layouts.
6
+ * Disk deposit for file hosts: init-deposit `writeSlashCommandDeposit` (#3054).
7
+ * OpenClaw skills deposit: `depositOpenClawL2ProductCommands` (#3064) — not a
8
+ * fake `.openclaw/commands/` file emitter.
9
+ */
10
+ export { assertThinHostEmission, emitAllHostCommandFiles, emitHostCommandFiles, getHostCommandLayout, HOST_COMMAND_LAYOUTS, hostRelativePath, isSlashEmitterHostId, listSlashEmitterHosts, renderHostFileContents, SLASH_EMITTER_HOSTS, } from "./emitters.js";
11
+ export { BYTES_PER_TOKEN_ESTIMATE, estimateTokens, generateThinWrapper, generateThinWrappers, isThinWrapperMarkdown, MAX_CATALOG_TOKENS, MAX_DESCRIPTION_TOKENS, MAX_WRAPPER_BODY_TOKENS, measureTokenBudget, renderThinWrapperBody, renderThinWrapperFile, } from "./generator.js";
12
+ export { assertThinOpenClawArtifacts, generateOpenClawSkillArtifacts, isManagedOpenClawL2Skill, isManagedOpenClawRouterSkill, isThinOpenClawSkillMarkdown, listOpenClawManagedSkillSlugs, listOpenClawProductSkillSlugs, OPENCLAW_L2_MANAGED_MARKER, OPENCLAW_L2_ROUTER_MARKER, renderOpenClawProductSkillMarkdown, renderOpenClawRouterSkillMarkdown, } from "./openclaw-adapter.js";
13
+ export { DEFAULT_OPENCLAW_PRODUCT_COMMANDS, depositOpenClawL2ProductCommands, FIELD_OPENCLAW_PRODUCT_COMMANDS, FIELD_OPENCLAW_PRODUCT_COMMANDS_CLI_ALIAS, inspectOpenClawProductCommands, loadOpenClawProductCommandsPolicyFromProject, resolveOpenClawProductCommandsPolicy, validateOpenClawProductCommands, } from "./openclaw-deposit.js";
14
+ export { assertOpenClawSlugMapIntegrity, isValidOpenClawSlug, listOpenClawSlugEntries, logicalIdToOpenClawSlug, OPENCLAW_LOGICAL_ID_BY_SLUG, OPENCLAW_ROUTER_SLUG, OPENCLAW_SLUG_BY_LOGICAL_ID, OPENCLAW_SLUG_MAX_LEN, OPENCLAW_SLUG_PATTERN, openClawSlugToLogicalId, } from "./openclaw-slugs.js";
15
+ export { getProductCommand, listProductCommands, logicalIdToFilename, logicalIdToFilenameStem, PRODUCT_COMMAND_COUNT, PRODUCT_COMMANDS, } from "./product-set.js";
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,64 @@
1
+ /**
2
+ * OpenClaw-native L2 product-command adapter (#3064 D1–D3).
3
+ *
4
+ * Hybrid skills-first MVP:
5
+ * - One thin **router** user-invocable skill (`deft`) for native menus
6
+ * - 13 thin user-invocable skills mapped from the L2 product set
7
+ *
8
+ * Reuses {@link generateThinWrappers} / {@link listProductCommands} — no second
9
+ * name table. Bodies stay thin (L5). Not a fake `.openclaw/commands/` file emitter.
10
+ */
11
+ import { type ThinWrapperIR } from "./generator.js";
12
+ /** Ownership marker in managed OpenClaw L2 skill bodies (idempotent rewrite). */
13
+ export declare const OPENCLAW_L2_MANAGED_MARKER = "<!-- deft-managed: openclaw-l2-product-command -->";
14
+ /** Router role marker (distinct from the 13 product skills). */
15
+ export declare const OPENCLAW_L2_ROUTER_MARKER = "<!-- deft-openclaw-role: router -->";
16
+ /** One OpenClaw skill directory artifact ready for deposit (D4). */
17
+ export interface OpenClawSkillArtifact {
18
+ /** Directory name under workspace skills root (= OpenClaw slug). */
19
+ readonly slug: string;
20
+ /** Canonical product slash id, or null for the router. */
21
+ readonly logicalId: string | null;
22
+ readonly description: string;
23
+ /** Full SKILL.md contents. */
24
+ readonly skillMarkdown: string;
25
+ /** Content-relative dispatch path (null for router). */
26
+ readonly dispatchPath: string | null;
27
+ readonly role: "router" | "product";
28
+ readonly estimatedBodyTokens: number;
29
+ }
30
+ /**
31
+ * Render thin product skill body from shared IR (L5).
32
+ * OpenClaw frontmatter: name + description + user-invocable.
33
+ */
34
+ export declare function renderOpenClawProductSkillMarkdown(wrapper: ThinWrapperIR): string;
35
+ /**
36
+ * Router skill: one native-menu-facing entry that lists L2 slug dispatch (D1/D3).
37
+ * Keeps body thin — no inlined strategy/skill content.
38
+ */
39
+ export declare function renderOpenClawRouterSkillMarkdown(wrappers?: readonly ThinWrapperIR[]): string;
40
+ /** True when on-disk skill looks like a Directive-managed OpenClaw L2 artifact. */
41
+ export declare function isManagedOpenClawL2Skill(skillMarkdown: string): boolean;
42
+ /** True when content is the managed router skill. */
43
+ export declare function isManagedOpenClawRouterSkill(skillMarkdown: string): boolean;
44
+ /**
45
+ * Build router + 13 product skill artifacts from shared thin-wrapper IR.
46
+ *
47
+ * Count of product skills === {@link PRODUCT_COMMAND_COUNT}; plus one router.
48
+ */
49
+ export declare function generateOpenClawSkillArtifacts(wrappers?: readonly ThinWrapperIR[]): readonly OpenClawSkillArtifact[];
50
+ /** Product skill slugs only (no router), stable order. */
51
+ export declare function listOpenClawProductSkillSlugs(): readonly string[];
52
+ /** All managed skill directory names (router + 13). */
53
+ export declare function listOpenClawManagedSkillSlugs(): readonly string[];
54
+ /**
55
+ * Thinness guard for OpenClaw skills: managed marker + dispatch path present
56
+ * (product) or router marker; body token budget.
57
+ */
58
+ export declare function isThinOpenClawSkillMarkdown(skillMarkdown: string, options: {
59
+ dispatchPath?: string | null;
60
+ role: "router" | "product";
61
+ }): boolean;
62
+ /** Assert generated artifacts stay thin and cover the full L2 set. */
63
+ export declare function assertThinOpenClawArtifacts(artifacts?: readonly OpenClawSkillArtifact[]): void;
64
+ //# sourceMappingURL=openclaw-adapter.d.ts.map
@@ -0,0 +1,198 @@
1
+ /**
2
+ * OpenClaw-native L2 product-command adapter (#3064 D1–D3).
3
+ *
4
+ * Hybrid skills-first MVP:
5
+ * - One thin **router** user-invocable skill (`deft`) for native menus
6
+ * - 13 thin user-invocable skills mapped from the L2 product set
7
+ *
8
+ * Reuses {@link generateThinWrappers} / {@link listProductCommands} — no second
9
+ * name table. Bodies stay thin (L5). Not a fake `.openclaw/commands/` file emitter.
10
+ */
11
+ import { estimateTokens, generateThinWrappers, MAX_WRAPPER_BODY_TOKENS, } from "./generator.js";
12
+ import { assertOpenClawSlugMapIntegrity, listOpenClawSlugEntries, logicalIdToOpenClawSlug, OPENCLAW_ROUTER_SLUG, openClawSlugToLogicalId, } from "./openclaw-slugs.js";
13
+ import { listProductCommands, PRODUCT_COMMAND_COUNT } from "./product-set.js";
14
+ /** Ownership marker in managed OpenClaw L2 skill bodies (idempotent rewrite). */
15
+ export const OPENCLAW_L2_MANAGED_MARKER = "<!-- deft-managed: openclaw-l2-product-command -->";
16
+ /** Router role marker (distinct from the 13 product skills). */
17
+ export const OPENCLAW_L2_ROUTER_MARKER = "<!-- deft-openclaw-role: router -->";
18
+ function yamlSingleLine(value) {
19
+ if (/[:#{}[\],&*!|>'"%@`]|^\s|\s$/.test(value) || value === "") {
20
+ return JSON.stringify(value);
21
+ }
22
+ return value;
23
+ }
24
+ /**
25
+ * Render thin product skill body from shared IR (L5).
26
+ * OpenClaw frontmatter: name + description + user-invocable.
27
+ */
28
+ export function renderOpenClawProductSkillMarkdown(wrapper) {
29
+ const slug = logicalIdToOpenClawSlug(wrapper.logicalId);
30
+ const frontmatter = [
31
+ `name: ${slug}`,
32
+ `description: ${yamlSingleLine(wrapper.description)}`,
33
+ "user-invocable: true",
34
+ ];
35
+ if (wrapper.argumentHint !== undefined) {
36
+ frontmatter.push(`argument-hint: ${yamlSingleLine(wrapper.argumentHint)}`);
37
+ }
38
+ const body = [
39
+ OPENCLAW_L2_MANAGED_MARKER,
40
+ "",
41
+ `# ${slug}`,
42
+ "",
43
+ `Canonical slash: \`${wrapper.logicalId}\``,
44
+ "",
45
+ wrapper.bodyMarkdown.trimEnd(),
46
+ "",
47
+ ].join("\n");
48
+ return `---\n${frontmatter.join("\n")}\n---\n\n${body}`;
49
+ }
50
+ /**
51
+ * Router skill: one native-menu-facing entry that lists L2 slug dispatch (D1/D3).
52
+ * Keeps body thin — no inlined strategy/skill content.
53
+ */
54
+ export function renderOpenClawRouterSkillMarkdown(wrappers = generateThinWrappers()) {
55
+ const slugLines = wrappers
56
+ .map((w) => {
57
+ const slug = logicalIdToOpenClawSlug(w.logicalId);
58
+ return `- \`${slug}\` → \`${w.logicalId}\` (${w.dispatchPath})`;
59
+ })
60
+ .join("\n");
61
+ const description = "Directive L2 product-command router (prefer for native menus; Telegram budget)";
62
+ const frontmatter = [
63
+ `name: ${OPENCLAW_ROUTER_SLUG}`,
64
+ `description: ${yamlSingleLine(description)}`,
65
+ "user-invocable: true",
66
+ 'argument-hint: "<slug-or-logical-id>"',
67
+ ];
68
+ const body = [
69
+ OPENCLAW_L2_MANAGED_MARKER,
70
+ OPENCLAW_L2_ROUTER_MARKER,
71
+ "",
72
+ `# ${OPENCLAW_ROUTER_SLUG} (router)`,
73
+ "",
74
+ "Prefer this skill for native/Telegram menus (one slot). All 13 L2 commands remain invocable as `/<slug>` text skills when deposited.",
75
+ "",
76
+ "On invoke: match `$ARGUMENTS` to an OpenClaw slug or canonical `/deft…` id, then read and follow that command's content-relative dispatch path under `.deft/core/` when installed.",
77
+ "Do not inline strategy, skill, or commands.md bodies.",
78
+ "",
79
+ "L2 slug map:",
80
+ slugLines,
81
+ "",
82
+ ].join("\n");
83
+ return `---\n${frontmatter.join("\n")}\n---\n\n${body}`;
84
+ }
85
+ /** True when on-disk skill looks like a Directive-managed OpenClaw L2 artifact. */
86
+ export function isManagedOpenClawL2Skill(skillMarkdown) {
87
+ return skillMarkdown.includes(OPENCLAW_L2_MANAGED_MARKER);
88
+ }
89
+ /** True when content is the managed router skill. */
90
+ export function isManagedOpenClawRouterSkill(skillMarkdown) {
91
+ return (isManagedOpenClawL2Skill(skillMarkdown) && skillMarkdown.includes(OPENCLAW_L2_ROUTER_MARKER));
92
+ }
93
+ /**
94
+ * Build router + 13 product skill artifacts from shared thin-wrapper IR.
95
+ *
96
+ * Count of product skills === {@link PRODUCT_COMMAND_COUNT}; plus one router.
97
+ */
98
+ export function generateOpenClawSkillArtifacts(wrappers = generateThinWrappers()) {
99
+ assertOpenClawSlugMapIntegrity(listProductCommands());
100
+ if (wrappers.length !== PRODUCT_COMMAND_COUNT) {
101
+ throw new Error(`Expected ${PRODUCT_COMMAND_COUNT} thin wrappers for OpenClaw adapter, got ${wrappers.length}`);
102
+ }
103
+ const routerMd = renderOpenClawRouterSkillMarkdown(wrappers);
104
+ const routerBodyStart = routerMd.indexOf("\n---\n", 4);
105
+ const routerBody = routerBodyStart >= 0 ? routerMd.slice(routerBodyStart + 5) : routerMd;
106
+ const artifacts = [
107
+ {
108
+ slug: OPENCLAW_ROUTER_SLUG,
109
+ logicalId: null,
110
+ description: "Directive L2 product-command router (prefer for native menus; Telegram budget)",
111
+ skillMarkdown: routerMd,
112
+ dispatchPath: null,
113
+ role: "router",
114
+ estimatedBodyTokens: estimateTokens(routerBody),
115
+ },
116
+ ];
117
+ for (const w of wrappers) {
118
+ const skillMarkdown = renderOpenClawProductSkillMarkdown(w);
119
+ const bodyStart = skillMarkdown.indexOf("\n---\n", 4);
120
+ const body = bodyStart >= 0 ? skillMarkdown.slice(bodyStart + 5) : skillMarkdown;
121
+ artifacts.push({
122
+ slug: logicalIdToOpenClawSlug(w.logicalId),
123
+ logicalId: w.logicalId,
124
+ description: w.description,
125
+ skillMarkdown,
126
+ dispatchPath: w.dispatchPath,
127
+ role: "product",
128
+ estimatedBodyTokens: estimateTokens(body),
129
+ });
130
+ }
131
+ return artifacts;
132
+ }
133
+ /** Product skill slugs only (no router), stable order. */
134
+ export function listOpenClawProductSkillSlugs() {
135
+ return listOpenClawSlugEntries().map((e) => e.openClawSlug);
136
+ }
137
+ /** All managed skill directory names (router + 13). */
138
+ export function listOpenClawManagedSkillSlugs() {
139
+ return [OPENCLAW_ROUTER_SLUG, ...listOpenClawProductSkillSlugs()];
140
+ }
141
+ /**
142
+ * Thinness guard for OpenClaw skills: managed marker + dispatch path present
143
+ * (product) or router marker; body token budget.
144
+ */
145
+ export function isThinOpenClawSkillMarkdown(skillMarkdown, options) {
146
+ if (!isManagedOpenClawL2Skill(skillMarkdown))
147
+ return false;
148
+ if (!skillMarkdown.startsWith("---\n"))
149
+ return false;
150
+ if (!/^user-invocable:\s*true\s*$/m.test(skillMarkdown))
151
+ return false;
152
+ if (/^##\s+(Phase|Workflow|Steps|Acceptance)\b/m.test(skillMarkdown))
153
+ return false;
154
+ const close = skillMarkdown.indexOf("\n---\n", 4);
155
+ const body = close >= 0 ? skillMarkdown.slice(close + 5) : skillMarkdown;
156
+ // Router lists the full slug map; allow a slightly larger budget than file hosts.
157
+ const maxBody = options.role === "router" ? MAX_WRAPPER_BODY_TOKENS * 4 : MAX_WRAPPER_BODY_TOKENS + 40;
158
+ if (estimateTokens(body) > maxBody)
159
+ return false;
160
+ if (options.role === "router") {
161
+ return isManagedOpenClawRouterSkill(skillMarkdown);
162
+ }
163
+ if (!options.dispatchPath)
164
+ return false;
165
+ if (!body.includes(options.dispatchPath))
166
+ return false;
167
+ if (!body.includes("Read and follow"))
168
+ return false;
169
+ if (!body.includes("Do not inline"))
170
+ return false;
171
+ return true;
172
+ }
173
+ /** Assert generated artifacts stay thin and cover the full L2 set. */
174
+ export function assertThinOpenClawArtifacts(artifacts = generateOpenClawSkillArtifacts()) {
175
+ const products = artifacts.filter((a) => a.role === "product");
176
+ const routers = artifacts.filter((a) => a.role === "router");
177
+ if (routers.length !== 1) {
178
+ throw new Error(`Expected exactly 1 OpenClaw router skill, got ${routers.length}`);
179
+ }
180
+ if (products.length !== PRODUCT_COMMAND_COUNT) {
181
+ throw new Error(`Expected ${PRODUCT_COMMAND_COUNT} OpenClaw product skills, got ${products.length}`);
182
+ }
183
+ for (const a of artifacts) {
184
+ if (!isThinOpenClawSkillMarkdown(a.skillMarkdown, {
185
+ dispatchPath: a.dispatchPath,
186
+ role: a.role,
187
+ })) {
188
+ throw new Error(`Non-thin OpenClaw skill artifact: ${a.slug}`);
189
+ }
190
+ if (a.role === "product" && a.logicalId) {
191
+ const reverse = openClawSlugToLogicalId(a.slug);
192
+ if (reverse !== a.logicalId) {
193
+ throw new Error(`Slug/logicalId mismatch for ${a.slug}`);
194
+ }
195
+ }
196
+ }
197
+ }
198
+ //# sourceMappingURL=openclaw-adapter.js.map
@@ -0,0 +1,73 @@
1
+ /**
2
+ * OpenClaw L2 product-command skill deposit (#3064 D4–D5).
3
+ *
4
+ * Writes thin user-invocable skills into the OpenClaw **main workspace skills**
5
+ * root (copy/stage — not symlink-escape into npm). Idempotent managed rewrite;
6
+ * preserves consumer custom skills at the same slug.
7
+ *
8
+ * Primary operator path: `deft doctor --fix` when OpenClaw is detected.
9
+ * init/update also deposits when OC signals are present and policy is on.
10
+ *
11
+ * ⊗ Fake project `.openclaw/commands/` file emitter.
12
+ */
13
+ import { type OpenClawDetectResult } from "../doctor/openclaw-skills.js";
14
+ /** Typed policy key: opt-out of OpenClaw L2 product-command adapter deposit. */
15
+ export declare const FIELD_OPENCLAW_PRODUCT_COMMANDS = "plan.policy.openClawProductCommands";
16
+ export declare const FIELD_OPENCLAW_PRODUCT_COMMANDS_CLI_ALIAS = "openClawProductCommands";
17
+ /** Default: adapter on when OpenClaw is detected (real adapter — D5). */
18
+ export declare const DEFAULT_OPENCLAW_PRODUCT_COMMANDS = true;
19
+ export type OpenClawProductCommandsPolicy = boolean;
20
+ export interface OpenClawProductCommandsPolicyField {
21
+ readonly name: string;
22
+ readonly current: OpenClawProductCommandsPolicy;
23
+ readonly default: OpenClawProductCommandsPolicy;
24
+ readonly source: string;
25
+ }
26
+ export interface OpenClawL2DepositResult {
27
+ readonly changed: boolean;
28
+ /** Absolute skill directory paths written/updated. */
29
+ readonly writtenPaths: string[];
30
+ /** Absolute paths removed on opt-out (managed only). */
31
+ readonly removedPaths: string[];
32
+ /** Absolute paths skipped (consumer custom content). */
33
+ readonly preservedCustomPaths: string[];
34
+ readonly skillsDirs: string[];
35
+ readonly skipped: boolean;
36
+ readonly skipReason?: string;
37
+ readonly detect: OpenClawDetectResult | null;
38
+ }
39
+ export interface OpenClawL2DepositOptions {
40
+ /** Project root for policy load (optional; defaults enabled when absent). */
41
+ readonly projectRoot?: string;
42
+ readonly env?: NodeJS.ProcessEnv;
43
+ readonly homeDir?: string;
44
+ /** Multi-seat parity with doctor --openclaw-all-agents. */
45
+ readonly allAgents?: boolean;
46
+ /** Explicit policy override (tests). */
47
+ readonly policy?: OpenClawProductCommandsPolicy;
48
+ readonly printf?: (text: string) => void;
49
+ readonly isDir?: (path: string) => boolean;
50
+ readonly readDir?: (path: string) => string[];
51
+ /**
52
+ * When true, deposit even if OpenClaw is not detected (tests only).
53
+ * Production paths stay fail-closed when OC signals are absent (D5).
54
+ */
55
+ readonly forceDeposit?: boolean;
56
+ /** Override skills roots (tests). */
57
+ readonly skillsDirs?: readonly string[];
58
+ }
59
+ /** Resolve openClawProductCommands from raw policy value. */
60
+ export declare function resolveOpenClawProductCommandsPolicy(raw: unknown): OpenClawProductCommandsPolicy;
61
+ export declare function validateOpenClawProductCommands(value: unknown): string[];
62
+ export declare function inspectOpenClawProductCommands(data: Record<string, unknown> | null): OpenClawProductCommandsPolicyField;
63
+ export declare function loadOpenClawProductCommandsPolicyFromProject(projectRoot: string): OpenClawProductCommandsPolicy;
64
+ /**
65
+ * Deposit OpenClaw L2 product-command skills into workspace skills roots.
66
+ *
67
+ * Fail-closed when OpenClaw is not detected (unless forceDeposit for tests).
68
+ * Policy false → remove managed skills only (opt-out).
69
+ */
70
+ export declare function depositOpenClawL2ProductCommands(options?: OpenClawL2DepositOptions): OpenClawL2DepositResult;
71
+ /** Resolve absolute main skills dir for a state dir (test helper re-export path). */
72
+ export declare function resolveOpenClawMainSkillsDirForState(stateDir: string): string;
73
+ //# sourceMappingURL=openclaw-deposit.d.ts.map