Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

feat(limps): AI agent UX hardening — next_steps, sanitization, hooks, templates, exit codes#145

Open
paulbreuler wants to merge 8 commits intomainfrom
feat/ai-ux-hardening
Open

feat(limps): AI agent UX hardening — next_steps, sanitization, hooks, templates, exit codes#145
paulbreuler wants to merge 8 commits intomainfrom
feat/ai-ux-hardening

Conversation

@paulbreuler
Copy link
Owner

Summary

Implements 8 improvements to the limps MCP server derived from patterns in urdocs-old and hermes-agent reference projects. The primary goal is to improve AI agent UX: reduce inference round-trips, harden security, and add lightweight extensibility — without major architectural changes.

Changes

Next-steps guidance (reduce agent inference)

  • All core tool responses embed ordered next_steps arrays (NextStep[]) so agents know what to call next
  • JsonSuccess<T> envelope extended with optional next_steps field; wrapSuccess() accepts steps
  • New src/utils/tool-response.ts with toJsonText() and withNextSteps() helpers

Semantic exit codes

  • exitCodeForError() in errors.ts maps validation errors → exit 1, system errors → exit 2
  • handleJsonOutput uses it instead of hardcoded 1
  • Constants EXIT_SUCCESS/VALIDATION/SYSTEM/USAGE exported for scripting and CI/CD

Input sanitization + injection scanning

  • sanitizePlanName(): rejects null bytes, path traversal (..), enforces 100-char limit, normalises to slug
  • sanitizeDocPath(): rejects null bytes, bidi-override/zero-width/BOM chars, path traversal
  • scanForInjection(): non-blocking FTS5 content scan; logs console.error warning, never throws
  • Wired into create-plan, create-doc, update-doc, indexDocument

Filesystem event hooks

  • src/hooks/runner.ts discovers {event}.mjs files under ~/.limps/hooks/ (configurable via hooksPath)
  • 5-second timeout per hook (import + execution); errors logged, never propagated
  • Events: server:startup/shutdown, plan:created/deleted, agent:status-changed, doc:indexed/created/updated/deleted

Plan templates

  • create_plan / limps plan create accept templateName (default | spec)
  • New packages/limps/templates/spec.md — 12-section specification template
  • --template spec flag exposed on the CLI command

Tool availability guards

  • list_plans, get_next_task, list_agents return descriptive errors when plansPath doesn't exist
  • ping extended with structured { availability: { plansPath, docsPaths, db, graphDb } } report

Idempotent plan creation

  • create_plan / limps plan create accept ifExists: 'return' | 'fail' (default 'return')
  • Duplicate names return { alreadyExists: true, ...next_steps } as success — safe for agent retry patterns

Progressive disclosure resource

  • plans://meta/* MCP resource: SQLite-backed, returns { planId, name, taskCounts, source } without reading all agent files
  • Falls back to filesystem scan when plan not yet indexed

CLI fix

  • limps plan create previously rendered raw JSON as green text after the tool-response refactor
  • Now parses JSON payload and renders human-friendly Ink output

Tests

  • All 1576 tests pass across 139 test files
  • New test files:
    • tests/utils/sanitize-input.test.ts — 22 tests (null bytes, path traversal, Unicode, slug normalisation, injection scan)
    • tests/utils/tool-response.test.ts — 6 tests (withNextSteps, toJsonText)
    • tests/utils/errors-exit-codes.test.ts — 10 tests (exit code constants, exitCodeForError mapping)
    • tests/hooks/runner.test.ts — 5 tests (missing hooks dir, missing hook file, hook execution, error isolation)
  • Updated existing tests broken by idempotent create and JSON response format changes

Code Review

  • General review: ✅ Passed (architecture sound, clean separation of concerns)
  • Correctness: ✅ Fixed (SQL LIKE wildcard escaping in queryPlanMetaFromDb patched before merge)
  • MCP/LLM review: Not run
  • Commit review: Conventional commits, atomic, descriptive

Notes / Risks

  • scanForInjection is intentionally non-blocking — it logs warnings but never prevents indexing. This is by design to avoid losing legitimate content.
  • Hook sync functions execute to completion regardless of the timeout (timeout only applies to async phases). Noted in code comments.
  • The plans://meta/* resource is additive — existing plans://summary/* and plans://full/* resources are unchanged.
  • ifExists: 'return' (new default) is a behaviour change for create_plan: previously, duplicate names returned an error. Existing test suite updated to reflect the new default.

paulbreuler and others added 4 commits March 1, 2026 23:29
…ooks, templates

Implements 8 improvements derived from urdocs-old and hermes-agent patterns:

1. **next_steps guidance** — Tool responses embed ordered NextStep arrays so AI
   agents know what to call next without inference. New `src/utils/tool-response.ts`
   helper; applied to create_plan, get_next_task, update_task_status, list_plans,
   get_plan_status. CLI JsonSuccess envelope gains optional next_steps field.

2. **Semantic exit codes** — handleJsonOutput uses exitCodeForError() to map
   validation errors (NOT_FOUND, ALREADY_EXISTS, PLAN_NOT_FOUND) → exit 1,
   system errors (EACCES, ENOSPC) → exit 2. Constants in errors.ts.

3. **Input sanitization** — sanitizePlanName() rejects null bytes, path traversal,
   enforces 100-char limit; sanitizeDocPath() rejects bidi-override/zero-width chars.
   scanForInjection() does non-blocking FTS5 content scanning with console.error warning.
   Wired into create-plan, create-doc, update-doc, indexDocument.

4. **Filesystem event hooks** — src/hooks/runner.ts discovers {event}.mjs files under
   ~/.limps/hooks/ (configurable via hooksPath in ServerConfig). 5-second timeout,
   errors never propagate. Events: server:startup/shutdown, plan:created, plan:deleted,
   agent:status-changed, doc:indexed/created/updated/deleted.

5. **Richer plan templates** — create_plan accepts templateName ('default'|'spec').
   New packages/limps/templates/spec.md with 12-section structure (Goal, Context,
   Critical Requirements, Features, Acceptance Criteria, Non-Goals, Assumptions,
   Verification Plan, Rollback Plan, Success Metrics, Decision Log, Status).

6. **Tool availability guards** — list_plans, get_next_task, list_agents check
   existsSync(plansPath) and return descriptive errors. ping extended with structured
   {availability: {plansPath, docsPaths, db, graphDb}} report.

7. **Idempotent plan creation** — create_plan ifExists param ('return'|'fail', default
   'return'). On duplicate, returns {alreadyExists: true, ...next_steps} as success
   instead of error, enabling safe retry patterns for AI agents.

8. **Progressive disclosure resource** — plans://meta/* resource backed by SQLite
   queries returns lightweight {planId, name, taskCounts, source} without reading
   all agent files. Falls back to filesystem scan when not yet indexed.

All 1576 tests pass, npm run validate clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… human-friendly output

The create plan command was rendering raw JSON text as green output after
the tool-response refactor. This commit:

- Adds --template <default|spec> flag so users can select plan templates
- Adds --json flag for machine-readable JSON envelope with next_steps
- Parses JSON payload from handleCreatePlan and renders Ink UI:
    "Plan created: <name>" or "Plan already exists: <name>"
- Shows "Template: 12-section specification" hint for spec template

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…withTimeout, extractStatus

Applies to files added in the AI agent UX hardening feature:

- sanitize-input.ts: extract rejectNullBytes/rejectPathTraversal guards
  shared between sanitizePlanName and sanitizeDocPath (was copy-pasted)
- hooks/runner.ts: extract withTimeout<T> helper eliminating the duplicate
  Promise.race + setTimeout pattern used for import and fn-call phases
- errors.ts: extract codeToExitCode(code) collapsing two identical
  DocumentError/LimpsError branches in exitCodeForError into one
- plans-meta.ts: extract StatusCounts type + extractStatus(content) helper
  shared between DB and FS query paths; also reuse type in PlanMeta interface

No behaviour change. All 1576 tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
planDir paths containing % or _ would be treated as SQL LIKE wildcards,
causing unintended matches. Escape them with ESCAPE '\' before the query.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 2, 2026 14:51
The script contains machine-specific vault paths and should not
be committed. Added to .gitignore under a dedicated section.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the limps MCP server/CLI for better AI-agent UX by adding structured next_steps guidance, input sanitization and injection scanning, filesystem hooks, new plan templates, and semantic exit codes—plus a new lightweight plans://meta/* resource.

Changes:

  • Added next_steps support across tool/CLI JSON responses and introduced shared helpers for consistent JSON payloads.
  • Introduced sanitization/injection scanning utilities and wired them into document/plan flows, plus a filesystem-based hooks runner for lifecycle events.
  • Added plan templating improvements (spec template), idempotent plan creation behavior, ping availability reporting, and a new DB-backed plan meta resource.

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
packages/limps/tests/utils/tool-response.test.ts Adds tests for withNextSteps() and toJsonText() helpers.
packages/limps/tests/utils/sanitize-input.test.ts Adds tests for plan/doc sanitization and injection scan logging.
packages/limps/tests/utils/errors-exit-codes.test.ts Adds tests for exit code constants and mapping behavior.
packages/limps/tests/update-task-status.test.ts Updates tests to parse JSON tool output payloads.
packages/limps/tests/json-output.test.ts Updates expectations for semantic exit codes (generic error → 2).
packages/limps/tests/hooks/runner.test.ts Adds tests for the new hooks runner.
packages/limps/tests/create-plan.test.ts Updates tests for idempotent plan creation (ifExists).
packages/limps/tests/cli/create-plan.test.ts Updates CLI integration tests to parse JSON payloads.
packages/limps/templates/spec.md Adds a 12-section “spec” plan template.
packages/limps/src/utils/tool-response.ts Introduces helper functions for adding next_steps and serializing JSON.
packages/limps/src/utils/sanitize-input.ts Adds plan/doc sanitization and non-blocking injection scanning.
packages/limps/src/utils/errors.ts Adds semantic exit code constants and exitCodeForError().
packages/limps/src/types.ts Adds the NextStep type.
packages/limps/src/tools/update-task-status.ts Switches tool output to JSON text + hook emission + next-step suggestions.
packages/limps/src/tools/update-doc.ts Adds doc path sanitization and emits doc:updated hooks.
packages/limps/src/tools/ping.ts Extends ping response with availability diagnostics.
packages/limps/src/tools/list-plans.ts Adds plansPath availability guard + next-step guidance in JSON output.
packages/limps/src/tools/list-agents.ts Adds plansPath availability guard.
packages/limps/src/tools/get-plan-status.ts Adds next_steps suggestions and JSON output formatting.
packages/limps/src/tools/get-next-task.ts Adds plansPath guard + next_steps guidance for status updates.
packages/limps/src/tools/delete-doc.ts Emits doc:deleted hooks.
packages/limps/src/tools/create-plan.ts Adds templates, idempotent behavior, sanitization, hooks, and next-step guidance.
packages/limps/src/tools/create-doc.ts Adds doc path sanitization and emits doc:created hooks.
packages/limps/src/server-shared.ts Emits server:startup and server:shutdown hooks.
packages/limps/src/resources/plans-meta.ts Adds new plans://meta/* resource (DB fast-path with FS fallback).
packages/limps/src/resources/index.ts Registers the new “Plan Meta” resource URI and handler.
packages/limps/src/indexer.ts Adds injection scanning and emits doc:indexed hooks on indexing.
packages/limps/src/hooks/types.ts Defines hook events, payload, and function signature types.
packages/limps/src/hooks/runner.ts Implements hook discovery and execution with timeouts and error isolation.
packages/limps/src/config.ts Adds hooksPath config option.
packages/limps/src/commands/plan/create.tsx Fixes CLI rendering by parsing JSON tool output; adds template flag + JSON mode handling.
packages/limps/src/cli/json-output.ts Extends success envelope with optional next_steps and uses semantic exit codes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +41 to +51
* Resolve the filesystem path for a hook file.
* Returns `null` when no matching file exists.
*/
function resolveHookPath(hooksPath: string, event: HookEvent): string | null {
const candidates = [
join(hooksPath, `${event}.mjs`),
join(hooksPath, `${event}.js`),
join(hooksPath, event, 'index.mjs'),
join(hooksPath, event, 'index.js'),
];

Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hook filenames are derived directly from the event name (e.g. plan:created.mjs). On Windows, : is not a valid filename character, so this hook resolution scheme (and the documented examples) won't work on Windows installs. Consider mapping HookEvent → a filesystem-safe name (e.g., replace : with __ or URL-encode), while keeping the logical event name in the payload.

Suggested change
* Resolve the filesystem path for a hook file.
* Returns `null` when no matching file exists.
*/
function resolveHookPath(hooksPath: string, event: HookEvent): string | null {
const candidates = [
join(hooksPath, `${event}.mjs`),
join(hooksPath, `${event}.js`),
join(hooksPath, event, 'index.mjs'),
join(hooksPath, event, 'index.js'),
];
* Map a logical hook event name to a filesystem-safe key.
*
* Currently replaces `:` with `__` so that events like `plan:created`
* map to filenames/directories that are valid on Windows.
*/
function toFilesystemEventKey(event: HookEvent): string {
return event.replace(/:/g, '__');
}
/**
* Resolve the filesystem path for a hook file.
* Returns `null` when no matching file exists.
*/
function resolveHookPath(hooksPath: string, event: HookEvent): string | null {
const eventKey = toFilesystemEventKey(event);
const candidates: string[] = [
// Original event-based filenames/directories (backwards compatibility)
join(hooksPath, `${event}.mjs`),
join(hooksPath, `${event}.js`),
join(hooksPath, event, 'index.mjs'),
join(hooksPath, event, 'index.js'),
];
// Filesystem-safe variants for platforms where `:` is not a valid path character (e.g. Windows)
if (eventKey !== event) {
candidates.push(
join(hooksPath, `${eventKey}.mjs`),
join(hooksPath, `${eventKey}.js`),
join(hooksPath, eventKey, 'index.mjs'),
join(hooksPath, eventKey, 'index.js')
);
}

Copilot uses AI. Check for mistakes.
};
}

const planName = planDir.split('/').pop() ?? planId;
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

planName is derived via planDir.split('/'), which breaks on Windows paths (backslashes). Use path.basename(planDir) (and import it from path) to keep this resource handler cross-platform.

Copilot uses AI. Check for mistakes.
Comment on lines +339 to +340
// Non-blocking hook notification
void runHooks('doc:indexed', { path: filePath, title: metadata.title });
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

runHooks('doc:indexed', ...) is called without passing a hooksPath, so it will always use DEFAULT_HOOKS_PATH and ignore context.config.hooksPath (which other callers pass explicitly). If hooksPath is meant to be configurable globally, consider threading it into indexDocument() (e.g., an optional parameter) so indexing-triggered hooks run from the configured directory.

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +36
// Check DB writable
let dbWritable = false;
try {
db.prepare('SELECT 1').get();
dbWritable = true;
} catch {
dbWritable = false;
}
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dbWritable is set based on SELECT 1, which only proves the DB connection can read/execute statements—not that it's writable. Either rename this to something like dbReachable/dbReady, or perform a side-effect-free write check (e.g., create/drop a TEMP table) so the availability report is accurate.

Copilot uses AI. Check for mistakes.
Comment on lines 145 to 151
try {
// Sanitize path: reject null bytes, bidi overrides, and path traversal
sanitizeDocPath(path);

// Validate path
const validated = validatePath(path, repoRoot);

Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sanitizeDocPath() returns a trimmed/sanitized path, but the return value is ignored here, so the (trimmed) value is not what validatePath() and file operations use. Consider assigning the result (e.g., const sanitizedPath = sanitizeDocPath(path)) and using that for validation and subsequent logic.

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +54
// Escape LIKE metacharacters so a planDir containing % or _ is treated literally
const escaped = planDir.replace(/[%_]/g, '\\$&');
const rows = db
.prepare(
`SELECT content FROM documents WHERE path LIKE ? ESCAPE '\\' AND path LIKE '%/agents/%'`
)
.all(`${escaped}%`) as { content: string }[];
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

queryPlanMetaFromDb() sets ESCAPE '\\' but only escapes % and _ in planDir. If planDir contains backslashes (Windows) they will be treated as escape characters in the LIKE pattern and can cause incorrect matches/zero results. Also, the secondary filter path LIKE '%/agents/%' assumes / separators. Consider normalizing stored paths to POSIX separators (recommended), or escaping backslashes here and using a separator-agnostic filter.

Copilot uses AI. Check for mistakes.
Comment on lines +241 to +297
/**
* Semantic exit codes for CLI commands.
* Scripts and AI-driven pipelines can distinguish error categories.
*
* 0 = success
* 1 = validation error (bad input, NOT_FOUND, ALREADY_EXISTS, PLAN_NOT_FOUND)
* 2 = system error (DB lock, ENOSPC, EACCES, INTERNAL_ERROR)
* 3 = usage error (wrong flags, missing required args)
*/
export const EXIT_SUCCESS = 0;
export const EXIT_VALIDATION = 1;
export const EXIT_SYSTEM = 2;
export const EXIT_USAGE = 3;

const VALIDATION_CODES: ReadonlySet<string> = new Set([
'NOT_FOUND',
'ENOENT',
'ALREADY_EXISTS',
'EEXIST',
'PLAN_NOT_FOUND',
'TASK_NOT_FOUND',
'VALIDATION_ERROR',
'RESTRICTED_PATH',
]);

const SYSTEM_CODES: ReadonlySet<string> = new Set([
'EACCES',
'PERMISSION_DENIED',
'ENOSPC',
'INTERNAL_ERROR',
'EISDIR',
'ENOTDIR',
]);

/**
* Map a known error code string to a semantic exit code.
* Defaults to EXIT_VALIDATION for unrecognised codes on typed errors.
*/
function codeToExitCode(code: string): number {
if (VALIDATION_CODES.has(code)) return EXIT_VALIDATION;
if (SYSTEM_CODES.has(code)) return EXIT_SYSTEM;
return EXIT_VALIDATION;
}

/**
* Map an error to a semantic exit code.
*
* @param err - The error to map
* @returns Exit code (0, 1, 2, or 3)
*/
export function exitCodeForError(err: unknown): number {
if (err instanceof DocumentError || err instanceof LimpsError) {
return codeToExitCode(err.code);
}

return EXIT_SYSTEM;
}
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment/docs here claim exit codes can be 0/1/2/3 (including usage errors), but exitCodeForError() never returns EXIT_USAGE and there’s no mapping for common usage/arg-parse errors. Either implement a usage-error path (and map appropriate error types/codes to EXIT_USAGE), or update the documentation to avoid advertising an exit code that can’t be produced.

Copilot uses AI. Check for mistakes.
Comment on lines 91 to +95
} catch (err) {
setError((err as Error).message);
const message = (err as Error).message;
if (isJsonMode(options)) {
outputJson(wrapError(message, { code: 'CREATE_PLAN_FAILED' }), 1);
}
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In JSON mode, the catch path hardcodes exit code 1 (outputJson(..., 1)), which will misclassify system errors (e.g. DB open/permission failures) now that semantic exit codes exist. Consider using exitCodeForError(err) to choose the exit code for thrown errors so scripts/CI can distinguish validation vs system failures.

Copilot uses AI. Check for mistakes.
Comment on lines 103 to 109
try {
// Sanitize path: reject null bytes, bidi overrides, and path traversal
sanitizeDocPath(path);

// Validate path (must not exist, must be writable)
const validated = validatePath(path, repoRoot, {
requireWritable: true,
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sanitizeDocPath() returns a trimmed/sanitized path, but the return value is ignored. That means leading/trailing whitespace (and any future normalization done in sanitizeDocPath) won’t actually be applied to the path used by validatePath() and subsequent filesystem operations. Consider const sanitizedPath = sanitizeDocPath(path) and using sanitizedPath from that point onward.

Copilot uses AI. Check for mistakes.
Comment on lines +132 to +138
join(process.cwd(), 'templates', templateFileName),
join(__dirname, '..', '..', 'templates', templateFileName),
// For 'default' also try plan.md if spec.md not found
...(templateName === 'default'
? [
join(process.cwd(), 'templates', 'plan.md'),
join(__dirname, '..', '..', 'templates', 'plan.md'),
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new spec template is resolved from .../templates/spec.md relative to the package source tree. However, packages/limps/package.json only publishes dist/ (no templates/), so in an installed package this lookup will likely fail and silently fall back to the minimal built-in template (making templateName: 'spec' ineffective unless users create templates/spec.md in their CWD). Consider bundling/copying templates/*.md into dist (and/or adding templates to the published files list) and resolving against the runtime location.

Suggested change
join(process.cwd(), 'templates', templateFileName),
join(__dirname, '..', '..', 'templates', templateFileName),
// For 'default' also try plan.md if spec.md not found
...(templateName === 'default'
? [
join(process.cwd(), 'templates', 'plan.md'),
join(__dirname, '..', '..', 'templates', 'plan.md'),
// User-provided templates in the current working directory
join(process.cwd(), 'templates', templateFileName),
// Built-in templates shipped with the package, resolved relative to the runtime location
join(__dirname, 'templates', templateFileName),
// For 'default' also try plan.md if spec.md not found
...(templateName === 'default'
? [
join(process.cwd(), 'templates', 'plan.md'),
join(__dirname, 'templates', 'plan.md'),

Copilot uses AI. Check for mistakes.
paulbreuler and others added 3 commits March 2, 2026 11:12
…ds, and INVEST

## Phase 1 — CLI abbreviation & help redesign
- Add GROUP_ALIASES map (gn→gen, pl→plan, dc→docs, gr→graph, hl→health, sv→server, cf→config, pr→proposals)
- Expand argv aliases before Pastel app.run() in cli.tsx
- Redesign root help to gh-style concise listing with inline abbreviations
- Add 'gen' group to completion engine and ROOT_COMMANDS

## Phase 2 — Document model + templates + config extensions
- IDocument interface with Persona, Journey, Spec, AgentTask concrete classes
- serializeDocument/serializeFrontmatter with YAML frontmatter rendering
- resolvePrimer() — 3-tier resolution: override > plan PRIMER.md > global config
- parseDocument() factory via gray-matter
- validateInvest() — full INVEST criteria scoring (I/N/V/E/S/T, 0-100 score, warnings)
- Templates: persona.md, journey.md, agent.md, primer.md
- ServerConfig extended: workspacePath, personasPath, journeysPath, specsPath, primerPath, acpPort

## Phase 3 — Gen command group + INVEST CLI + MCP tools
- limps gen persona/journey/spec/agent/agents/primer subcommands
- limps plan check invest <plan> — INVEST compliance table for all agents
- generate_artifact, validate_invest, get_primer MCP tools registered in CORE_TOOL_NAMES
- gen agent --strict fails with exit 1 if INVEST score < 60

## Phase 4 — ACP hub
- AcpServer: HTTP server routing POST /acp/{tool} to 5 tool handlers
- AcpSessionManager: EventEmitter tracking agent sessions + lifecycle events
- Tools: claim_task, update_status, create_artifact, validate_invest, list_tasks
- acpPort config field; server wires into server-main startup

## Phase 5 — TUI mission control dashboard
- Three-pane Ink layout: Plans tree | Live ACP feed | Context + INVEST health
- useAcpEvents hook subscribing to AcpSessionManager events
- usePlanTree hook (DB integration TODO: #149)
- limps tui command launching full dashboard with ACP server

## Security fix — symlink traversal in process_doc (Critical)
- process_doc was missing checkSymlinkAncestors + checkPathSafety calls present in process_docs
- Confirmed exploitable: symlink inside plans dir → /etc/passwd readable via MCP
- Fix: symlink ancestor check moved before existsSync so path components are validated
  even when the final target file does not exist
- TDD: 4 new tests in tests/tools/process-doc.test.ts (legit file, /etc symlink,
  parent dir symlink, ../ traversal)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… gen commands

Export JSON_OPTION from json-output.ts as single source of truth for the
--json flag schema. All 6 gen commands (persona, journey, spec, agent,
agents, primer) now import and use it instead of inline z.boolean()
definitions, fixing blank help text on --json in gen command --help output.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants