@karmaniverous/jeeves 0.5.8 → 0.5.10

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.
@@ -9,6 +9,7 @@ import { execSync, spawnSync } from 'node:child_process';
9
9
  import { randomUUID } from 'node:crypto';
10
10
  import { lock } from 'proper-lockfile';
11
11
  import { fileURLToPath } from 'node:url';
12
+ import Handlebars from 'handlebars';
12
13
  import { packageDirectorySync } from 'package-directory';
13
14
 
14
15
  function getDefaultExportFromCjs (x) {
@@ -53,31 +54,31 @@ var hasRequiredExtraTypings;
53
54
  function requireExtraTypings () {
54
55
  if (hasRequiredExtraTypings) return extraTypings.exports;
55
56
  hasRequiredExtraTypings = 1;
56
- (function (module, exports$1) {
57
+ (function (module, exports) {
57
58
  const commander = require$$0;
58
59
 
59
- exports$1 = module.exports = {};
60
+ exports = module.exports = {};
60
61
 
61
62
  // Return a different global program than commander,
62
63
  // and don't also return it as default export.
63
- exports$1.program = new commander.Command();
64
+ exports.program = new commander.Command();
64
65
 
65
66
  /**
66
67
  * Expose classes. The FooT versions are just types, so return Commander original implementations!
67
68
  */
68
69
 
69
- exports$1.Argument = commander.Argument;
70
- exports$1.Command = commander.Command;
71
- exports$1.CommanderError = commander.CommanderError;
72
- exports$1.Help = commander.Help;
73
- exports$1.InvalidArgumentError = commander.InvalidArgumentError;
74
- exports$1.InvalidOptionArgumentError = commander.InvalidArgumentError; // Deprecated
75
- exports$1.Option = commander.Option;
70
+ exports.Argument = commander.Argument;
71
+ exports.Command = commander.Command;
72
+ exports.CommanderError = commander.CommanderError;
73
+ exports.Help = commander.Help;
74
+ exports.InvalidArgumentError = commander.InvalidArgumentError;
75
+ exports.InvalidOptionArgumentError = commander.InvalidArgumentError; // Deprecated
76
+ exports.Option = commander.Option;
76
77
 
77
- exports$1.createCommand = (name) => new commander.Command(name);
78
- exports$1.createOption = (flags, description) =>
78
+ exports.createCommand = (name) => new commander.Command(name);
79
+ exports.createOption = (flags, description) =>
79
80
  new commander.Option(flags, description);
80
- exports$1.createArgument = (name, description) =>
81
+ exports.createArgument = (name, description) =>
81
82
  new commander.Argument(name, description);
82
83
  } (extraTypings, extraTypings.exports));
83
84
  return extraTypings.exports;
@@ -182,8 +183,6 @@ const WORKSPACE_FILES = {
182
183
  };
183
184
  /** Skill directory name within workspace. */
184
185
  const SKILLS_DIR = 'skills';
185
- /** Jeeves skill directory name. */
186
- const JEEVES_SKILL_DIR = 'jeeves';
187
186
  /** Templates directory name within core config. */
188
187
  const TEMPLATES_DIR = 'templates';
189
188
  /** Core config file name. */
@@ -269,14 +268,14 @@ const PLATFORM_COMPONENTS = [
269
268
  * Core library version, inlined at build time.
270
269
  *
271
270
  * @remarks
272
- * The `0.5.7` placeholder is replaced by
271
+ * The `0.5.9` placeholder is replaced by
273
272
  * `@rollup/plugin-replace` during the build with the actual version
274
273
  * from `package.json`. This ensures the correct version survives
275
274
  * when consumers bundle core into their own dist (where runtime
276
275
  * `import.meta.url`-based resolution would find the wrong package.json).
277
276
  */
278
277
  /** The core library version from package.json (inlined at build time). */
279
- const CORE_VERSION = '0.5.7';
278
+ const CORE_VERSION = '0.5.9';
280
279
 
281
280
  /**
282
281
  * Runtime Node.js version floor check.
@@ -333,6 +332,11 @@ const workspaceCoreConfigSchema = z
333
332
  configRoot: z.string().optional().describe('Platform config root path'),
334
333
  /** OpenClaw gateway URL. */
335
334
  gatewayUrl: z.string().optional().describe('OpenClaw gateway URL'),
335
+ /** Dev repo paths keyed by component name. */
336
+ devRepos: z
337
+ .record(z.string(), z.string())
338
+ .optional()
339
+ .describe('Dev repo paths by component name'),
336
340
  })
337
341
  .partial();
338
342
  /** Memory shared config section. */
@@ -347,13 +351,6 @@ const workspaceMemoryConfigSchema = z
347
351
  .max(1)
348
352
  .optional()
349
353
  .describe('Memory warning threshold'),
350
- /** Staleness threshold in days. */
351
- staleDays: z
352
- .number()
353
- .int()
354
- .positive()
355
- .optional()
356
- .describe('Memory staleness threshold in days'),
357
354
  })
358
355
  .partial();
359
356
  /** Workspace config Zod schema. */
@@ -381,7 +378,6 @@ const WORKSPACE_CONFIG_DEFAULTS = {
381
378
  memory: {
382
379
  budget: 20_000,
383
380
  warningThreshold: 0.8,
384
- staleDays: 30,
385
381
  },
386
382
  };
387
383
  /**
@@ -512,7 +508,6 @@ function resolveCliConfig(opts) {
512
508
  memory: {
513
509
  budget: resolveConfigValue(undefined, readNumericEnv('JEEVES_MEMORY_BUDGET'), fileConfig?.memory?.budget, WORKSPACE_CONFIG_DEFAULTS.memory.budget),
514
510
  warningThreshold: resolveConfigValue(undefined, readNumericEnv('JEEVES_MEMORY_WARNING_THRESHOLD'), fileConfig?.memory?.warningThreshold, WORKSPACE_CONFIG_DEFAULTS.memory.warningThreshold),
515
- staleDays: resolveConfigValue(undefined, readNumericEnv('JEEVES_MEMORY_STALE_DAYS'), fileConfig?.memory?.staleDays, WORKSPACE_CONFIG_DEFAULTS.memory.staleDays),
516
511
  },
517
512
  };
518
513
  }
@@ -821,9 +816,8 @@ function getServiceUrl(serviceName, consumerName) {
821
816
  if (coreUrl)
822
817
  return coreUrl;
823
818
  // 3. Fall back to port constants
824
- const port = DEFAULT_PORTS[serviceName];
825
- if (port !== undefined) {
826
- return `http://127.0.0.1:${String(port)}`;
819
+ if (serviceName in DEFAULT_PORTS) {
820
+ return `http://127.0.0.1:${String(DEFAULT_PORTS[serviceName])}`;
827
821
  }
828
822
  throw new Error(`jeeves-core: unknown service "${serviceName}" and no config found`);
829
823
  }
@@ -1178,7 +1172,7 @@ Periodic checks (email, calendar, mentions) belong in jeeves-runner scripts, not
1178
1172
  ## Platform Surface Conventions
1179
1173
 
1180
1174
  **Slack:**
1181
- - No threaded replies by default
1175
+ - Never initiate a threaded reply. Only reply within a thread started by a human.
1182
1176
  - Use \`<#C…>\` for channel references
1183
1177
 
1184
1178
  **Table formatting:** On channels that do not support Markdown tables (Slack, Discord, WhatsApp, IRC), use code-block tables with aligned columns. Markdown tables are only safe in contexts that render them (GitHub, jeeves-server, files).
@@ -1384,8 +1378,8 @@ Never manually edit \`~/.openclaw/extensions/\`. Always use the CLI commands abo
1384
1378
 
1385
1379
  ### Reference Templates
1386
1380
 
1387
- <!-- IF_TEMPLATES -->
1388
- Reference templates are available at \`__TEMPLATE_PATH__\`:
1381
+ {{#if templatePath}}
1382
+ Reference templates are available at \`{{templatePath}}\`:
1389
1383
 
1390
1384
  | Template | Purpose |
1391
1385
  |----------|---------|
@@ -1393,9 +1387,39 @@ Reference templates are available at \`__TEMPLATE_PATH__\`:
1393
1387
  | \`spec-to-code-guide.md\` | The spec-to-code development practice — 7-stage iterative process, convergence loops, release gates |
1394
1388
 
1395
1389
  Read these templates when creating new specs, onboarding to new projects, or when asked about the development process.
1396
- <!-- ELSE_TEMPLATES -->
1390
+ {{else}}
1397
1391
  > Reference templates not yet installed. Run \`npx @karmaniverous/jeeves install\` to seed templates.
1398
- <!-- ENDIF_TEMPLATES -->
1392
+ {{/if}}
1393
+
1394
+ ### Post-Upgrade Maintenance
1395
+
1396
+ After updating OpenClaw (\`npm install -g openclaw@latest\` or equivalent), reinstall all Jeeves component plugins to repair install state:
1397
+
1398
+ \`\`\`bash
1399
+ npx @karmaniverous/jeeves install
1400
+ npx @karmaniverous/jeeves-runner-openclaw install
1401
+ npx @karmaniverous/jeeves-watcher-openclaw install
1402
+ npx @karmaniverous/jeeves-server-openclaw install
1403
+ npx @karmaniverous/jeeves-meta-openclaw install
1404
+ \`\`\`
1405
+
1406
+ Then restart the gateway. Plugin installers copy dist files and patch config; reinstalling after an OpenClaw update ensures the extensions directory stays consistent.
1407
+
1408
+ ### Source Code Preference
1409
+
1410
+ {{#if devRepos}}
1411
+ When investigating, debugging, or analyzing Jeeves components, always read TypeScript source from dev repos — never compiled \`dist/\` from the global npm install. Dev repos:
1412
+
1413
+ | Component | Dev Repo |
1414
+ |-----------|----------|
1415
+ {{#each devRepos}}
1416
+ | {{@key}} | \`{{this}}\` |
1417
+ {{/each}}
1418
+
1419
+ Built code is minified, harder to reason about, and wastes context. Always \`git pull\` before analysis.
1420
+ {{else}}
1421
+ > Dev repo paths not configured. Add \`core.devRepos\` to \`jeeves.config.json\` to enable source code preference guidance.
1422
+ {{/if}}
1399
1423
  `;
1400
1424
 
1401
1425
  /**
@@ -1844,6 +1868,10 @@ async function updateManagedSection(filePath, content, options = {}) {
1844
1868
  * Platform template with live data, and writes managed sections using
1845
1869
  * `updateManagedSection`.
1846
1870
  */
1871
+ /** Compiled Handlebars template for the Platform section (cached at module level). */
1872
+ const compiledPlatformTemplate = Handlebars.compile(toolsPlatformTemplate, {
1873
+ noEscape: true,
1874
+ });
1847
1875
  /**
1848
1876
  * Resolve the package's content directory for template file copying.
1849
1877
  *
@@ -1887,23 +1915,13 @@ function copyTemplates(coreConfigDir) {
1887
1915
  cpSync(sourceDir, destDir, { recursive: true });
1888
1916
  }
1889
1917
  /**
1890
- * Render the Platform template using simple string replacement.
1918
+ * Render the Platform template using Handlebars.
1891
1919
  *
1892
- * @param templatePath - Path to the templates directory.
1920
+ * @param context - Template context with optional templatePath and devRepos.
1893
1921
  * @returns Rendered platform content string.
1894
1922
  */
1895
- function renderPlatformTemplate(templatePath) {
1896
- const templatesAvailable = existsSync(templatePath);
1897
- let content = toolsPlatformTemplate;
1898
- // Handle <!-- IF_TEMPLATES --> ... <!-- ELSE_TEMPLATES --> ... <!-- ENDIF_TEMPLATES --> block
1899
- const ifRegex = /<!-- IF_TEMPLATES -->([\s\S]*?)<!-- ELSE_TEMPLATES -->([\s\S]*?)<!-- ENDIF_TEMPLATES -->/;
1900
- const match = ifRegex.exec(content);
1901
- if (match) {
1902
- content = content.replace(match[0], templatesAvailable ? match[1] : match[2]);
1903
- }
1904
- // Replace __TEMPLATE_PATH__ with the actual path
1905
- content = content.replace(/__TEMPLATE_PATH__/g, templatePath);
1906
- return content;
1923
+ function renderPlatformTemplate(context) {
1924
+ return compiledPlatformTemplate(context);
1907
1925
  }
1908
1926
  /**
1909
1927
  * Refresh platform content: SOUL.md, AGENTS.md, and TOOLS.md Platform section.
@@ -1911,7 +1929,7 @@ function renderPlatformTemplate(templatePath) {
1911
1929
  * @param options - Configuration for the refresh cycle.
1912
1930
  */
1913
1931
  async function refreshPlatformContent(options) {
1914
- const { coreVersion, componentName, componentVersion, servicePackage, pluginPackage, stalenessThresholdMs, } = options;
1932
+ const { coreVersion, componentName, componentVersion, servicePackage, pluginPackage, stalenessThresholdMs, workspaceConfig, } = options;
1915
1933
  const workspacePath = getWorkspacePath();
1916
1934
  const coreConfigDir = getCoreConfigDir();
1917
1935
  // 1. Write calling component's version entry
@@ -1925,7 +1943,11 @@ async function refreshPlatformContent(options) {
1925
1943
  }
1926
1944
  // 2. Render Platform template
1927
1945
  const templatePath = join(coreConfigDir, TEMPLATES_DIR);
1928
- const platformContent = renderPlatformTemplate(templatePath);
1946
+ const wsConfig = workspaceConfig ?? loadWorkspaceConfig(workspacePath);
1947
+ const platformContent = renderPlatformTemplate({
1948
+ templatePath: existsSync(templatePath) ? templatePath : undefined,
1949
+ devRepos: wsConfig?.core?.devRepos,
1950
+ });
1929
1951
  // 3. Write TOOLS.md Platform section
1930
1952
  const toolsPath = join(workspacePath, WORKSPACE_FILES.tools);
1931
1953
  await updateManagedSection(toolsPath, platformContent, {
@@ -1955,7 +1977,158 @@ async function refreshPlatformContent(options) {
1955
1977
  copyTemplates(coreConfigDir);
1956
1978
  }
1957
1979
 
1958
- var skillContent = `---
1980
+ var codingContent = `---
1981
+ name: coding
1982
+ description: Engineering standards for all code work. Use when writing code, reviewing PRs, spawning coding sub-agents, or making architectural decisions in any project (not just Jeeves). Covers design-first development, schema-first patterns, testing, STAN workflow, dependency management, and pre-PR checklist.
1983
+ ---
1984
+
1985
+ # Engineering Standards
1986
+
1987
+ These standards apply to ALL code work — whether done directly or via sub-agents.
1988
+ When spawning sub-agents for coding tasks, include the relevant rules in the task prompt.
1989
+ Sub-agents don't inherit your context — if you don't pass the rules, they don't exist.
1990
+
1991
+ ---
1992
+
1993
+ ## Design-First Development
1994
+
1995
+ 1. **Iterate on design until convergence** — Summarize requirements, propose approach, raise questions BEFORE writing code.
1996
+ 2. **Services-first architecture** — Core logic in services behind ports; adapters thin; side effects at boundaries.
1997
+ 3. **Schema-first** — Runtime schema (Zod) is source of truth; TypeScript types derived via \`z.infer<>\`; validation centralized. Plain TypeScript \`interface\` declarations for config surfaces are not acceptable.
1998
+ 4. **300 LOC hard limit** — If a file would exceed 300 lines, stop and decompose first. No exceptions.
1999
+ 5. **Avoid \`any\`** — Prefer \`unknown\` + narrowing; if unavoidable, narrowest scope + rationale.
2000
+ 6. **Test pairing** — Every non-trivial module gets a \`*.test.ts\`.
2001
+ 7. **Open-source first** — Prefer established deps over home-grown solutions. Search npm/GitHub before building anything non-trivial.
2002
+
2003
+ ## Module Design
2004
+
2005
+ - **Single Responsibility** applies to modules as well as functions.
2006
+ - Prefer many small modules over a few large ones.
2007
+ - Keep module boundaries explicit and cohesive; avoid "kitchen-sink" files.
2008
+ - Co-locate tests with modules for discoverability.
2009
+
2010
+ ## Config Surfaces
2011
+
2012
+ - Define config with **Zod schemas** — never bare TypeScript interfaces.
2013
+ - Derive types: \`type MyConfig = z.infer<typeof myConfigSchema>\`
2014
+ - Generate **JSON Schema** from Zod for IDE DX (\`\$schema\` pointer in config files).
2015
+ - Validate at load time — fail fast with clear error messages.
2016
+ - \`init\` commands generate config with \`\$schema\` pointer already in place.
2017
+
2018
+ ## Testing
2019
+
2020
+ - **Unit tests** for pure services (no fs/process/network).
2021
+ - **Integration tests** for adapters/seams (minimal end-to-end slices).
2022
+ - Exercise happy paths AND representative error paths.
2023
+ - Table-driven cases encouraged for exhaustive coverage.
2024
+ - Keep coverage meaningful — prefer covering branches/decisions over chasing 100% lines.
2025
+
2026
+ ## STAN-Enabled Repos
2027
+
2028
+ When working in a repo with \`.stan/\`:
2029
+ - Run \`stan run --sequential --no-archive\` **before** each commit. Scripts must pass before you commit. Sequential runs are preferred to limit side effects. Archives are not needed (you won't use them).
2030
+ - **Push after every commit.** Don't accumulate unpushed local commits. Jason needs to be able to see your work at any time.
2031
+ - All scripts must pass before claiming work is complete.
2032
+ - Read \`.stan/output/<script>.txt\` for evidence on failures.
2033
+ - When creating stan scripts, eliminate colorized output where possible (e.g. \`--no-color\`, \`NO_COLOR=1\`) to reduce noise in script output files.
2034
+
2035
+ ## Cross-Package Verification
2036
+
2037
+ When changes affect exports consumed by another repo:
2038
+ - Standalone scripts passing ≠ "ready for review."
2039
+ - Use \`npm link\` or equivalent to verify the consumer builds against your changes.
2040
+ - Only claim completion when BOTH repos pass.
2041
+
2042
+ ## Dependencies: Latest Versions Required (HARD GATE)
2043
+
2044
+ **NEVER use a superseded version of ANY dependency without direct human authorization.** When adding a new dependency — or creating a new project — ALWAYS check the latest stable version and use it. This applies to runtime deps, dev deps, and peer deps alike.
2045
+
2046
+ - Before \`npm install <package>\`: run \`npm view <package> version\` (or check npmjs.com) to confirm you're installing the current major.
2047
+ - Before spawning sub-agents that install packages: include the latest version in the task prompt, or instruct the sub-agent to verify latest before installing.
2048
+ - If the latest major has known breaking issues that block adoption, flag it to the human — don't silently pin an old major.
2049
+
2050
+ LLMs are trained on stale data. Your training cutoff means you will default to old versions of everything. **Assume your version knowledge is wrong** and verify before every install.
2051
+
2052
+ *Earned: 2026-05-12, created the jeeves-tools repo with Zod 3 despite Zod 4 being available since mid-2025. Shipped 84 commits on the old major before catching it.*
2053
+
2054
+ ## Dependencies: Local Over Global
2055
+
2056
+ - **Dev dependencies belong in the project, not the global environment.** Install with \`npm install --save-dev\`, not \`npm install -g\`.
2057
+ - This guarantees reproducibility across machines and CI. Global installs mask environment differences that cause "works on my machine" failures.
2058
+ - **Rare exceptions:** Tools that are genuinely machine-level utilities (e.g. \`stan-cli\`). If in doubt, install locally.
2059
+ ## Dependency Failures
2060
+
2061
+ When a third-party dependency is broken:
2062
+ 1. Summarize the failure concisely.
2063
+ 2. Enumerate options: switch dependency → fix upstream → temporary pin → shim (last resort).
2064
+ 3. Recommend with rationale.
2065
+ 4. Do NOT immediately code around the problem.
2066
+
2067
+ ## CHANGELOG
2068
+
2069
+ - **Do not manually update CHANGELOG.md** — it is generated as part of the release process (e.g. via \`standard-version\`, \`changesets\`, or equivalent). Conventional commit messages are the input; the tooling produces the output.
2070
+
2071
+ ## Pre-PR Checklist (HARD GATE)
2072
+
2073
+ **Before creating ANY PR, run the full verification sequence. No exceptions.**
2074
+
2075
+ 1. \`stan run --sequential --no-archive\` if \`.stan/\` exists — this is the canonical check suite
2076
+ 2. In monorepos: run checks **from each package directory**, not just the root. Root-level runs may mask package-level failures due to config resolution differences.
2077
+ 3. Exercise the release path: check \`release-it\` hooks (or equivalent) in each releasable package — run the same commands (\`lint\`, \`typecheck\`, \`test\`, \`build\`) from the same cwd the release process uses.
2078
+
2079
+ If any step fails, fix it before committing. Do NOT create the PR and "note" the failures. Do NOT claim pre-existing failures without having actually run the commands first. Skipping this sequence is how we ship broken code and fabricate diagnoses.
2080
+
2081
+ - **Compare against canonical template** (\`karmaniverous/npm-package-template-ts\`) before any npm package PR. If the project is behind the template, update it to conform. If the template is behind the project, raise the issue with Jason for template upkeep.
2082
+ - **Run \`ncu --peer\`** before any PR. Review the output. Update safe patches/minors. **Flag major version bumps for discussion** — never auto-apply \`ncu -u\` without reading what changed. Peer dep conflicts must be resolved, not ignored.
2083
+ - When spawning sub-agents, include \`ncu --peer\` in the quality gate commands: \`ncu --peer && npm run lint && npm run typecheck && npm run build && npm test\`. The sub-agent should report \`ncu\` output and only apply updates that don't involve major bumps or peer conflicts.
2084
+ - **Resolve ALL script warnings.** It is NOT acceptable to release code with outstanding warnings. They exist for a reason — fix them.
2085
+ - **Typecheck/lint rules apply to ALL authored code**, including configs at project root (\`eslint.config.ts\`, \`rollup.config.ts\`, \`vitest.config.ts\`, etc.). Only generated code (e.g. typedoc output) should be excepted from code quality checks.
2086
+ - **Never disable lint/typecheck rules** without surfacing it for discussion first. Disabled rules are a major code smell. If a rule must be disabled, document the rationale inline at the point of suppression.
2087
+ - **Multiple tsconfigs are a code smell.** Sometimes needed, but often they paper over poor configuration choices. Fix root causes rather than adding tsconfig variants.
2088
+ - **In a TS repo, all scripts should be authored in TS** (not JS). Prefer execution with \`tsx\`.
2089
+ - **Clean-room verify before claiming "all green."** Run \`rimraf node_modules && npm install && npm run build\` (or equivalent) to catch issues masked by cached state. If someone reports an error you can't reproduce, assume your cache is lying — not that they're wrong.
2090
+ - Verify build, test, and lint pass after updates.
2091
+
2092
+ ## Dev Workspace
2093
+
2094
+ - **Clone location:** \`D:\\repos\\{org-or-userid}\\{repo}\` (e.g. \`D:\\repos\\karmaniverous\\jeeves-watcher\`)
2095
+ - D drive is the dev workspace. Do not clone repos elsewhere.
2096
+ - Fresh clones are preferred over copying existing checkouts — \`npm install\` from registry is faster than disk-copying \`node_modules\`.
2097
+ - D drive is NOT indexed by jeeves-watcher. Dev work stays off the archive.
2098
+
2099
+ ## GitHub Auth (HARD GATE)
2100
+
2101
+ - **ALL GitHub operations use \`jgs-jeeves\` auth.** Set \`GH_TOKEN\` before any \`gh\` CLI command:
2102
+ \`\`\`powershell
2103
+ \$env:GH_TOKEN = (Get-Content "J:\\config\\credentials\\github\\jgs-jeeves.token" -Raw).Trim()
2104
+ \`\`\`
2105
+ - Never write to GitHub as \`karmaniverous\` — that's Jason's account.
2106
+ - If \`jgs-jeeves\` lacks permissions, **escalate to Jason** rather than falling back to \`karmaniverous\`.
2107
+
2108
+ ## Issue Hygiene
2109
+
2110
+ - **Always comment rationale when closing an issue without resolution** (duplicate, won't-fix, obsolete). The close action alone doesn't explain why.
2111
+ - Reference the replacement issue/PR when closing as duplicate.
2112
+
2113
+ ## Code Style
2114
+
2115
+ - Prettier is source of truth for formatting.
2116
+ - Keep imports sorted per repo tooling.
2117
+ - Avoid dead code.
2118
+ - TSDoc \`@module\` or \`@packageDocumentation\` on every non-test module.
2119
+ - First 160 chars of module doc should be high-signal: what it does, IO/side effects, traversal hints.
2120
+
2121
+ ## eslint-disable is a HARD GATE
2122
+
2123
+ Never disable lint/typecheck rules without surfacing it for discussion first. Fix the code, don't suppress the warning. For test mocks, use properly typed partial objects (\`Partial<RealType>\`, typed \`MockReply\` interfaces) instead of \`any\`. Tests are code.
2124
+
2125
+ ## Git Merge Policy
2126
+
2127
+ - **No squash merges.** Preserve commit history.
2128
+ - **PR reviewer:** When creating a PR under \`jgs-jeeves\` auth, always add \`karmaniverous\` (Jason) as a reviewer.
2129
+ `;
2130
+
2131
+ var jeevesContent = `---
1959
2132
  name: jeeves
1960
2133
  description: Jeeves platform architecture, data flow, component interaction, scripts repo, and coordination knowledge. Use when making architectural decisions, coordinating across components, checking platform health, managing service lifecycle, or working with the scripts repo.
1961
2134
  ---
@@ -2019,7 +2192,7 @@ Managed blocks are stationary after initial insertion. Cleanup detection uses Ja
2019
2192
 
2020
2193
  \`jeeves.config.json\` at workspace root provides shared defaults:
2021
2194
  - Precedence: CLI flags → env vars → file → defaults
2022
- - Namespaced: \`core.*\` (workspace, configRoot, gatewayUrl) and \`memory.*\` (budget, warningThreshold, staleDays)
2195
+ - Namespaced: \`core.*\` (workspace, configRoot, gatewayUrl, devRepos) and \`memory.*\` (budget, warningThreshold)
2023
2196
  - Inspect with \`jeeves config [jsonpath]\`
2024
2197
 
2025
2198
  ## HEARTBEAT Protocol
@@ -2079,26 +2252,304 @@ When a workspace file exceeds the warning threshold, a \`## {filename}\` alert a
2079
2252
  Each file heading follows the same declined/active lifecycle as component headings. Users can decline alerts by changing the heading to \`## {filename}: declined\` (e.g., \`## AGENTS.md: declined\`).
2080
2253
  `;
2081
2254
 
2255
+ var operationsContent = `---
2256
+ name: operations
2257
+ description: Operational knowledge for a Jeeves installation. Covers date formatting utilities, email pipeline architecture, curation signal protocol, label taxonomy, and data flow patterns. Use when working with date formatting, email scripts, debugging email pipeline issues, or understanding how human email actions are interpreted.
2258
+ ---
2259
+
2260
+ # Operations
2261
+
2262
+ Operational knowledge for the Jeeves platform. Covers email pipeline architecture, curation protocols, and operational conventions.
2263
+
2264
+ ## Date Formatting
2265
+
2266
+ A \`date-fns\` wrapper lives at \`{configRoot}/jeeves-core/scripts/src/lib/dates.ts\`. It provides:
2267
+
2268
+ | Export | Purpose |
2269
+ |--------|---------|
2270
+ | \`dayOfWeek(dateStr)\` | Full weekday name for an ISO date string (e.g. \`'Monday'\`) |
2271
+ | \`formatDate(dateStr, fmt)\` | Format with any date-fns pattern |
2272
+ | \`relativeDays(dateStr, refStr?)\` | Human-friendly relative description (\`'today'\`, \`'tomorrow'\`, \`'3 days ago'\`) |
2273
+ | \`parseISO\` / \`format\` | Re-exported from date-fns for direct use |
2274
+
2275
+ ### Gateway session usage
2276
+
2277
+ From a gateway session, call via \`exec\`:
2278
+
2279
+ \`\`\`
2280
+ node -e "import { dayOfWeek } from './src/lib/dates.js'; console.log(dayOfWeek('2026-05-11'));"
2281
+ \`\`\`
2282
+
2283
+ with \`workdir: {configRoot}/jeeves-core/scripts\`.
2284
+
2285
+ Or use the simpler inline form when the full wrapper isn't needed:
2286
+
2287
+ \`\`\`
2288
+ node -e "import { format, parseISO } from 'date-fns'; console.log(format(parseISO('2026-05-11'), 'EEEE'));"
2289
+ \`\`\`
2290
+
2291
+ with \`workdir: {configRoot}/jeeves-core/scripts\` (so date-fns resolves from \`node_modules\`).
2292
+
2293
+ ### Hard gate
2294
+
2295
+ NEVER state a day of the week without computing it first. LLMs cannot do day-of-week arithmetic reliably.
2296
+
2297
+ ## Email Curation Signal Protocol
2298
+
2299
+ Defines how human email actions in Gmail are interpreted by Jeeves email processes.
2300
+
2301
+ ### Human Signals
2302
+
2303
+ | Signal | Meaning | Action |
2304
+ |--------|---------|--------|
2305
+ | Label added | Human is adjusting Jeeves classification | Update domain process inputs to reflect new classification |
2306
+ | Label removed | Human is adjusting Jeeves classification (removal) | Update domain process inputs to reflect removed classification |
2307
+ | Archived → Inbox | Human wants to keep this email in sight | Add \`watch\` label via update queue |
2308
+ | Starred / Flagged | Elevated attention — email is important in context | Domain processes should weight higher |
2309
+ | Moved to Spam | Confirmed spam — human classified as junk | Learn from classification for future triage |
2310
+ | Removed from Spam | False positive — human rescued from spam | Process as normal email, learn from false positive |
2311
+
2312
+ ### Watch Label
2313
+
2314
+ The \`watch\` label has special semantics:
2315
+ - When present, never auto-archive the email
2316
+ - When a watched email lands in archive (by anyone), remove the watch label
2317
+ - Added automatically when a human moves an archived email back to inbox
2318
+
2319
+ ### Label Taxonomy
2320
+
2321
+ Labels applied by Jeeves processes fall into two categories:
2322
+
2323
+ **Mechanical labels** (applied by domain extractors with high confidence):
2324
+ - \`meeting\` — meeting-related email (invite, notes, transcript)
2325
+ - \`finance\` — financial email (receipt, invoice, billing, statement)
2326
+
2327
+ **Reasoning labels** (applied by Update Email Meta, requiring cross-domain context):
2328
+ - \`project/<name>\` — associated with a known project
2329
+ - \`todo\` — requires action or work from the user
2330
+ - \`reply\` — someone is waiting on a response
2331
+ - \`alert\` — automated notification from a service
2332
+ - \`readme\` — newsletter or subscribed informational content
2333
+
2334
+ ### Labeling Principles
2335
+
2336
+ 1. Label at the earliest point where confidence is high enough
2337
+ 2. Domain extractors label what they know with certainty
2338
+ 3. Update Email Meta labels what requires cross-domain context
2339
+ 4. A thread can have multiple labels
2340
+ 5. Do not re-label threads that already have the label
2341
+ 6. **Prefer false negatives over false positives**
2342
+
2343
+ ### Poll Scope
2344
+
2345
+ Query: \`newer_than:1d in:anywhere\`
2346
+
2347
+ Must include spam and trash to detect human curation signals (e.g., moving to/from spam).
2348
+
2349
+ ## Email Pipeline Architecture
2350
+
2351
+ ### Directory Layout
2352
+
2353
+ \`\`\`
2354
+ {configRoot}/jeeves-core/email-config.json — pipeline configuration (accounts, buckets)
2355
+ {workspace}/../email/threads/{account}/ — canonical email archive (thread.json + per-message JSONs)
2356
+ \`\`\`
2357
+
2358
+ ### Data Flow
2359
+
2360
+ 1. **Poll** (\`email/poll.ts\`) — searches Gmail for recent threads, classifies, enqueues important ones for metadata fetch
2361
+ 2. **Fetch** (\`email/email-fetch.ts\`) — fetches full thread metadata from Gmail, creates/updates \`thread.json\` cache, enqueues for body download
2362
+ 3. **Download** (\`email/download.ts\`) — downloads full message bodies, writes per-message JSONs to \`threads/{account}/{threadId}/\`
2363
+ 4. **Drain Updates** (\`email/drain-updates.ts\`) — applies label changes and other queued updates back to Gmail
2364
+ 5. **Meta synthesis** — jeeves-meta synthesizes email archives into searchable summaries
2365
+
2366
+ ### thread.json (Cache Format)
2367
+
2368
+ Each \`threads/{account}/{threadId}/thread.json\` contains:
2369
+ - \`threadId\`, \`account\`, \`subject\`, \`participants\`
2370
+ - \`messages\` — record of \`{ messageId → { from, to, cc, date, internalDateMs, labels, snippet, attachments } }\`
2371
+ - \`provenance\` — label change history
2372
+ - \`cachedAt\`, \`updatedAt\`
2373
+
2374
+ ### Per-Message JSONs
2375
+
2376
+ Each \`threads/{account}/{threadId}/{messageId}.json\` contains full message data:
2377
+ - \`messageId\`, \`threadId\`, \`account\`, \`subject\`, \`from\`, \`to\`, \`cc\`
2378
+ - \`date\` (RFC 2822), \`internalDateMs\` (epoch ms)
2379
+ - \`labels\`, \`body\`, \`attachments\`, \`downloadedAt\`
2380
+ `;
2381
+
2382
+ var playbooksContent = `---
2383
+ name: playbooks
2384
+ description: >
2385
+ Reusable operational workflow patterns for the Jeeves platform. Use when asked to
2386
+ set up a daily briefing for a person or team, create standing meeting ops (notes +
2387
+ agenda generation), replicate an existing workflow pattern for a new context, or
2388
+ understand how recurring intelligence/ops workflows are structured. Covers the
2389
+ full stack: content directory, TASK files, standing orders, runner jobs, dispatcher
2390
+ scripts, Slack channel integration, and meta synthesis.
2391
+ ---
2392
+
2393
+ # Playbooks
2394
+
2395
+ Proven, replicable operational patterns. Each playbook describes what it does, what
2396
+ infrastructure it needs, and how to instantiate a new instance.
2397
+
2398
+ ## Common Infrastructure
2399
+
2400
+ All playbooks share these building blocks:
2401
+
2402
+ | Component | Purpose |
2403
+ |-----------|---------|
2404
+ | **Content directory** | \`{workspace}/../<silo>/<domain>/\` — stores output files, \`.meta/\`, standing orders |
2405
+ | **TASK file** | Markdown prompt that defines the LLM session's entire job |
2406
+ | **Standing orders** | \`standing-orders.md\` — append-only file for persistent stakeholder preferences |
2407
+ | **Dispatcher script** | TypeScript in \`{configRoot}/jeeves-core/scripts/src/\` — reads TASK, spawns worker |
2408
+ | **Runner job** | jeeves-runner job with cron schedule, timezone, and rrstack |
2409
+ | **Slack channel** | Delivery surface — summary posts, quick-link pins, feedback loop |
2410
+ | **Meta entity** | \`.meta/\` directory seeded so jeeves-meta synthesizes context over time |
2411
+
2412
+ ### Dispatcher Pattern
2413
+
2414
+ All dispatchers use \`taskFileDispatcher\` from \`dispatchers/lib/task-file-dispatcher.ts\`:
2415
+
2416
+ \`\`\`typescript
2417
+ import { taskFileDispatcher } from '../dispatchers/lib/task-file-dispatcher.js';
2418
+
2419
+ taskFileDispatcher({
2420
+ scriptName: '<silo>/<job-name>',
2421
+ jobId: '<runner-job-id>',
2422
+ taskFile: '<path-to-TASK.md>',
2423
+ timeout: 600,
2424
+ injectDateContext: true,
2425
+ dateTimezone: '<IANA timezone>',
2426
+ });
2427
+ \`\`\`
2428
+
2429
+ \`injectDateContext: true\` prepends an authoritative date line so the LLM knows today's date.
2430
+
2431
+ ### Standing Orders Convention
2432
+
2433
+ - Append-only — never modify existing entries
2434
+ - TASK files instruct the LLM to read standing orders at Step 0
2435
+ - TASK files instruct the LLM to append new persistent preferences from channel feedback
2436
+ - Include initial configuration section with participants, timezones, channel rules
2437
+
2438
+ ## Available Playbook Patterns
2439
+
2440
+ | Pattern | Description |
2441
+ |---------|-------------|
2442
+ | **Daily Briefing** | Recurring intelligence or action-item report for a stakeholder |
2443
+ | **Standing Meeting Ops** | Post-meeting notes + next-day agenda generation for a recurring meeting |
2444
+
2445
+ ## Instantiation Checklist
2446
+
2447
+ When creating a new playbook instance:
2448
+
2449
+ 1. Choose the appropriate pattern from the table above
2450
+ 2. Create the content directory with \`.meta/\` and \`standing-orders.md\`
2451
+ 3. Write the TASK file(s) — adapt from an existing instance. Ensure Step 0 reads feedback from the *delivery channel* (where output is posted), not only a DM
2452
+ 4. Write the dispatcher script(s) in \`{configRoot}/jeeves-core/scripts/src/<silo>/\`
2453
+ 5. Register the runner job(s) with appropriate cron, timezone, rrstack
2454
+ 6. Set up the Slack channel — pin a quick-links message if the pattern calls for it
2455
+ 7. Seed \`.meta/\` so meta synthesis begins
2456
+ 8. Test with \`--dry-run\` before going live
2457
+ `;
2458
+
2459
+ var slackBotProvisionerContent = `---
2460
+ name: slack-bot-provisioner
2461
+ description: Provision a new Slack bot identity for Clawdbot on a fresh server. Guides through Slack App creation steps, collects tokens, writes local config/env, and verifies connectivity.
2462
+ ---
2463
+
2464
+ # Slack bot provisioner (per-bot server)
2465
+
2466
+ Use this when you are setting up **a new Clawdbot instance** that should have **its own Slack bot identity** (one Slack App per bot), and you want a repeatable guided setup.
2467
+
2468
+ This skill assumes:
2469
+ - The user is a Slack workspace admin.
2470
+ - Each bot runs on its own server with its own Gateway config.
2471
+
2472
+ ## What can be automated vs not
2473
+
2474
+ **Automated (this skill):**
2475
+ - Create local folders.
2476
+ - Write a \`slack.env\` (bot token, signing secret).
2477
+ - Patch the Clawdbot gateway config to enable Slack for this instance (user approves).
2478
+ - Run a connectivity test (send a message to a channel).
2479
+
2480
+ **Not fully automatable (Slack-side):**
2481
+ - Creating/installing the Slack App and granting scopes (UI/OAuth).
2482
+ - Verifying event subscription URLs (requires public HTTPS endpoint).
2483
+
2484
+ ## Recommended mode
2485
+ Start with **outbound-only** (post messages) and expand to event subscriptions later.
2486
+
2487
+ ## Quick start
2488
+
2489
+ 1) Have the user do the Slack UI steps in \`references/slack-app-checklist.md\`.
2490
+ 2) Run the provisioning script:
2491
+
2492
+ - PowerShell:
2493
+ - \`powershell -NoProfile -ExecutionPolicy Bypass -File scripts/provision.ps1\`
2494
+
2495
+ The script will prompt for:
2496
+ - bot name
2497
+ - Slack bot token (\`xoxb-...\`)
2498
+ - Slack signing secret
2499
+ - (optional) test channel id
2500
+
2501
+ ## Files
2502
+ - Script: \`scripts/provision.ps1\`
2503
+ - Reference checklist: \`references/slack-app-checklist.md\`
2504
+ - Reference scopes: \`references/scopes.md\`
2505
+
2506
+ ## Secrets (best practices)
2507
+ - **Do not** store secrets inside the skill folder (skills are meant to be shareable/publishable).
2508
+ - Store secrets **per-instance** in the Clawdbot runtime directory (recommended):
2509
+ - \`C:\\Users\\Administrator\\.clawdbot\\credentials\\...\`
2510
+ - Prefer environment variables / local credential files loaded by the Gateway/service manager.
2511
+ - Never paste Slack secrets into public chats.
2512
+
2513
+ ## Safety notes
2514
+ - Store secrets per-instance. Do not reuse tokens across bot identities.
2515
+ - Avoid bot-to-bot loops: bots should ignore messages from other bots by default.
2516
+ `;
2517
+
2082
2518
  /**
2083
- * Skill seeding: write the `jeeves` workspace skill unconditionally.
2519
+ * Skill seeding: write all bundled platform skills to the workspace.
2084
2520
  *
2085
2521
  * @remarks
2086
- * The skill file is entirely generated — no user-authored content (Decision 48).
2087
- * Every installer (core CLI and component plugins) writes it unconditionally.
2522
+ * Skill files are entirely generated — no user-authored content (Decision 48).
2523
+ * Every installer (core CLI and component plugins) writes them unconditionally.
2088
2524
  * Content is inlined at build time via `rollup-plugin-md.ts`.
2525
+ *
2526
+ * @module
2089
2527
  */
2528
+ /** Map of skill directory name to inlined content. */
2529
+ const BUNDLED_SKILLS = {
2530
+ jeeves: jeevesContent,
2531
+ coding: codingContent,
2532
+ 'slack-bot-provisioner': slackBotProvisionerContent,
2533
+ operations: operationsContent,
2534
+ playbooks: playbooksContent,
2535
+ };
2090
2536
  /**
2091
- * Seed the jeeves workspace skill file.
2537
+ * Seed all bundled platform skills into the workspace.
2538
+ *
2539
+ * @remarks
2540
+ * Writes each skill to `{workspace}/skills/{name}/SKILL.md`, creating
2541
+ * directories as needed. Overwrites existing content unconditionally.
2092
2542
  *
2093
2543
  * @param workspacePath - Workspace root directory.
2094
2544
  */
2095
- function seedSkill(workspacePath) {
2096
- const skillDir = join(workspacePath, SKILLS_DIR, JEEVES_SKILL_DIR);
2097
- if (!existsSync(skillDir)) {
2098
- mkdirSync(skillDir, { recursive: true });
2545
+ function seedSkills(workspacePath) {
2546
+ for (const [name, content] of Object.entries(BUNDLED_SKILLS)) {
2547
+ const skillDir = join(workspacePath, SKILLS_DIR, name);
2548
+ if (!existsSync(skillDir)) {
2549
+ mkdirSync(skillDir, { recursive: true });
2550
+ }
2551
+ writeFileSync(join(skillDir, 'SKILL.md'), content, 'utf-8');
2099
2552
  }
2100
- const skillPath = join(skillDir, 'SKILL.md');
2101
- writeFileSync(skillPath, skillContent, 'utf-8');
2102
2553
  }
2103
2554
 
2104
2555
  /**
@@ -2159,8 +2610,8 @@ async function seedContent(options) {
2159
2610
  content: `- ${NOT_INSTALLED_ALERTS[name]}`,
2160
2611
  }));
2161
2612
  await writeHeartbeatSection(heartbeatPath, entries);
2162
- // Seed jeeves workspace skill (Decision 48: overwrite-on-install)
2163
- seedSkill(getWorkspacePath());
2613
+ // Seed all bundled platform skills (Decision 48: overwrite-on-install)
2614
+ seedSkills(getWorkspacePath());
2164
2615
  }
2165
2616
 
2166
2617
  /**
@@ -2203,46 +2654,21 @@ function registerInstallCommand(program) {
2203
2654
  }
2204
2655
 
2205
2656
  /**
2206
- * Memory budget accounting and staleness detection for MEMORY.md.
2657
+ * Memory budget accounting for MEMORY.md.
2207
2658
  *
2208
2659
  * @remarks
2209
- * Scans MEMORY.md for ISO date patterns in H2/H3 headings and bullet items.
2210
- * Reports character count against a configured budget, warning threshold state,
2211
- * and stale section candidates. Does not auto-delete: review remains
2212
- * human- or agent-mediated (Decision 42).
2660
+ * Reports character count against a configured budget and warning threshold
2661
+ * state. Does not auto-delete: review remains human- or agent-mediated
2662
+ * (Decision 42). Staleness detection removed in v0.5.9 (Decision 45).
2213
2663
  */
2214
- /** ISO date pattern: YYYY-MM-DD. */
2215
- const ISO_DATE_RE = /\b(\d{4}-\d{2}-\d{2})\b/g;
2216
- /** H2 heading pattern used to split sections. */
2217
- const H2_RE = /^## /m;
2218
2664
  /**
2219
- * Extract the most recent ISO date from a string.
2220
- *
2221
- * @param text - Text to scan for dates.
2222
- * @returns The most recent date found, or undefined.
2223
- */
2224
- function extractMostRecentDate(text) {
2225
- const matches = text.match(ISO_DATE_RE);
2226
- if (!matches)
2227
- return undefined;
2228
- let latest;
2229
- for (const match of matches) {
2230
- const d = new Date(match + 'T00:00:00Z');
2231
- if (!Number.isNaN(d.getTime())) {
2232
- if (!latest || d > latest)
2233
- latest = d;
2234
- }
2235
- }
2236
- return latest;
2237
- }
2238
- /**
2239
- * Analyze MEMORY.md for budget and staleness.
2665
+ * Analyze MEMORY.md for budget health.
2240
2666
  *
2241
2667
  * @param options - Analysis configuration.
2242
2668
  * @returns Memory hygiene result.
2243
2669
  */
2244
2670
  function analyzeMemory(options) {
2245
- const { workspacePath, budget, warningThreshold, staleDays } = options;
2671
+ const { workspacePath, budget, warningThreshold } = options;
2246
2672
  const memoryPath = join(workspacePath, WORKSPACE_FILES.memory);
2247
2673
  if (!existsSync(memoryPath)) {
2248
2674
  return {
@@ -2252,8 +2678,6 @@ function analyzeMemory(options) {
2252
2678
  usage: 0,
2253
2679
  warning: false,
2254
2680
  overBudget: false,
2255
- staleCandidates: 0,
2256
- staleSectionNames: [],
2257
2681
  };
2258
2682
  }
2259
2683
  const content = readFileSync(memoryPath, 'utf-8');
@@ -2261,21 +2685,6 @@ function analyzeMemory(options) {
2261
2685
  const usage = budget > 0 ? charCount / budget : charCount > 0 ? Infinity : 0;
2262
2686
  const warning = usage >= warningThreshold;
2263
2687
  const overBudget = usage > 1;
2264
- // Split into H2 sections and scan for staleness
2265
- const sections = content.split(H2_RE).slice(1); // skip content before first H2
2266
- const now = Date.now();
2267
- const thresholdMs = staleDays * 24 * 60 * 60 * 1000;
2268
- const staleSectionNames = [];
2269
- for (const section of sections) {
2270
- const sectionName = section.split('\n')[0]?.trim() ?? '';
2271
- const recentDate = extractMostRecentDate(section);
2272
- // Sections without dates are evergreen — never flagged (Decision 47)
2273
- if (!recentDate)
2274
- continue;
2275
- if (now - recentDate.getTime() > thresholdMs) {
2276
- staleSectionNames.push(sectionName);
2277
- }
2278
- }
2279
2688
  return {
2280
2689
  exists: true,
2281
2690
  charCount,
@@ -2283,8 +2692,6 @@ function analyzeMemory(options) {
2283
2692
  usage,
2284
2693
  warning,
2285
2694
  overBudget,
2286
- staleCandidates: staleSectionNames.length,
2287
- staleSectionNames,
2288
2695
  };
2289
2696
  }
2290
2697
 
@@ -2383,7 +2790,6 @@ function registerStatusCommand(program) {
2383
2790
  workspacePath: getWorkspacePath(),
2384
2791
  budget: resolved.memory.budget.value,
2385
2792
  warningThreshold: resolved.memory.warningThreshold.value,
2386
- staleDays: resolved.memory.staleDays.value,
2387
2793
  });
2388
2794
  console.log('Memory hygiene');
2389
2795
  console.log('-'.repeat(60));
@@ -2398,7 +2804,6 @@ function registerStatusCommand(program) {
2398
2804
  ? '⚠ Warning'
2399
2805
  : '✅ OK';
2400
2806
  console.log(`Chars: ${String(memory.charCount)} / ${String(memory.budget)} (${String(usagePct)}%) — ${status}`);
2401
- console.log(`Stale candidates: ${String(memory.staleCandidates)}`);
2402
2807
  }
2403
2808
  console.log();
2404
2809
  if (!allHealthy) {