devworkspace 0.3.0 → 0.3.2

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 (119) hide show
  1. package/CHANGELOG.md +42 -1
  2. package/README.md +8 -5
  3. package/dist/agents/claude/adapter.d.ts +3 -14
  4. package/dist/agents/claude/adapter.js +46 -49
  5. package/dist/agents/claude/adapter.js.map +1 -1
  6. package/dist/agents/codex/adapter.d.ts +2 -0
  7. package/dist/agents/codex/adapter.js +61 -0
  8. package/dist/agents/codex/adapter.js.map +1 -0
  9. package/dist/agents/copilot/adapter.d.ts +2 -0
  10. package/dist/agents/copilot/adapter.js +62 -0
  11. package/dist/agents/copilot/adapter.js.map +1 -0
  12. package/dist/agents/cursor/adapter.d.ts +2 -0
  13. package/dist/agents/cursor/adapter.js +127 -0
  14. package/dist/agents/cursor/adapter.js.map +1 -0
  15. package/dist/agents/index.d.ts +15 -0
  16. package/dist/agents/index.js +54 -0
  17. package/dist/agents/index.js.map +1 -0
  18. package/dist/agents/shared.d.ts +20 -0
  19. package/dist/agents/shared.js +76 -0
  20. package/dist/agents/shared.js.map +1 -0
  21. package/dist/agents/types.d.ts +16 -1
  22. package/dist/agents/types.js.map +1 -1
  23. package/dist/cli/args.d.ts +42 -0
  24. package/dist/cli/args.js +12 -0
  25. package/dist/cli/args.js.map +1 -1
  26. package/dist/cli/help.js +64 -10
  27. package/dist/cli/help.js.map +1 -1
  28. package/dist/cli/render.d.ts +12 -1
  29. package/dist/cli/render.js +188 -0
  30. package/dist/cli/render.js.map +1 -1
  31. package/dist/cli/run.js +100 -9
  32. package/dist/cli/run.js.map +1 -1
  33. package/dist/config/schemas.d.ts +6 -0
  34. package/dist/config/schemas.js +6 -0
  35. package/dist/config/schemas.js.map +1 -1
  36. package/dist/core/version.d.ts +7 -0
  37. package/dist/core/version.js +13 -0
  38. package/dist/core/version.js.map +1 -1
  39. package/dist/core/workspace.d.ts +1 -0
  40. package/dist/core/workspace.js +1 -0
  41. package/dist/core/workspace.js.map +1 -1
  42. package/dist/discovery/commands.d.ts +1 -0
  43. package/dist/discovery/commands.js +3 -1
  44. package/dist/discovery/commands.js.map +1 -1
  45. package/dist/features/schema.d.ts +3 -0
  46. package/dist/features/schema.js +79 -0
  47. package/dist/features/schema.js.map +1 -0
  48. package/dist/features/types.d.ts +77 -0
  49. package/dist/features/types.js +2 -0
  50. package/dist/features/types.js.map +1 -0
  51. package/dist/features/validate.d.ts +14 -0
  52. package/dist/features/validate.js +124 -0
  53. package/dist/features/validate.js.map +1 -0
  54. package/dist/index.d.ts +8 -2
  55. package/dist/index.js +4 -1
  56. package/dist/index.js.map +1 -1
  57. package/dist/integrations/adapters/claudePlugin.d.ts +12 -0
  58. package/dist/integrations/adapters/claudePlugin.js +37 -8
  59. package/dist/integrations/adapters/claudePlugin.js.map +1 -1
  60. package/dist/integrations/adapters/pluginSkill.js +5 -0
  61. package/dist/integrations/adapters/pluginSkill.js.map +1 -1
  62. package/dist/security/files.js +10 -2
  63. package/dist/security/files.js.map +1 -1
  64. package/dist/security/redact.js +1 -1
  65. package/dist/security/redact.js.map +1 -1
  66. package/dist/services/agent.d.ts +12 -2
  67. package/dist/services/agent.js +96 -14
  68. package/dist/services/agent.js.map +1 -1
  69. package/dist/services/analysis.js +3 -3
  70. package/dist/services/analysis.js.map +1 -1
  71. package/dist/services/doctor.js +65 -14
  72. package/dist/services/doctor.js.map +1 -1
  73. package/dist/services/drift.d.ts +21 -0
  74. package/dist/services/drift.js +89 -0
  75. package/dist/services/drift.js.map +1 -0
  76. package/dist/services/features.d.ts +22 -0
  77. package/dist/services/features.js +105 -0
  78. package/dist/services/features.js.map +1 -0
  79. package/dist/services/graph.d.ts +97 -0
  80. package/dist/services/graph.js +342 -0
  81. package/dist/services/graph.js.map +1 -0
  82. package/dist/services/refresh.js +15 -0
  83. package/dist/services/refresh.js.map +1 -1
  84. package/dist/services/reset.js +45 -5
  85. package/dist/services/reset.js.map +1 -1
  86. package/dist/services/setup.d.ts +56 -0
  87. package/dist/services/setup.js +116 -0
  88. package/dist/services/setup.js.map +1 -0
  89. package/dist/services/status.d.ts +8 -0
  90. package/dist/services/status.js +78 -10
  91. package/dist/services/status.js.map +1 -1
  92. package/dist/services/tasks.js +6 -1
  93. package/dist/services/tasks.js.map +1 -1
  94. package/dist/services/verify.d.ts +11 -0
  95. package/dist/services/verify.js +64 -2
  96. package/dist/services/verify.js.map +1 -1
  97. package/dist/tasks/routing.js +14 -10
  98. package/dist/tasks/routing.js.map +1 -1
  99. package/dist/tasks/task.js +6 -2
  100. package/dist/tasks/task.js.map +1 -1
  101. package/dist/verification/scope.js +1 -1
  102. package/dist/verification/scope.js.map +1 -1
  103. package/docs/agents.md +60 -0
  104. package/docs/architecture-report/01-executive-summary.md +168 -0
  105. package/docs/architecture-report/02-system-architecture.md +356 -0
  106. package/docs/architecture-report/03-tools-and-technologies.md +112 -0
  107. package/docs/architecture-report/04-execution-flows.md +257 -0
  108. package/docs/architecture-report/05-codebase-map.md +150 -0
  109. package/docs/architecture-report/06-command-reference.md +446 -0
  110. package/docs/architecture-report/07-data-flow-and-integrations.md +268 -0
  111. package/docs/architecture-report/08-build-test-deployment.md +185 -0
  112. package/docs/architecture-report/09-security-and-reliability.md +165 -0
  113. package/docs/architecture-report/10-feature-status.md +110 -0
  114. package/docs/architecture-report/11-glossary.md +97 -0
  115. package/docs/architecture-report/README.md +103 -0
  116. package/docs/architecture-report/diagrams.md +446 -0
  117. package/docs/architecture.md +2 -2
  118. package/docs/getting-started.md +38 -1
  119. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"workspace.js","sourceRoot":"","sources":["../../src/core/workspace.ts"],"names":[],"mappings":"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","sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { DevWorkspaceError } from '../errors/index.js';\nimport { atomicWriteJson, ensureDir, pathExists, readJsonFile } from '../utils/fs.js';\nimport { validateProject, type ProjectConfig, SCHEMA_VERSION } from '../config/schemas.js';\nimport { MIGRATIONS, migrateData, type Migration } from './migrations.js';\n\nexport const WORKSPACE_DIR = '.devworkspace';\n\nexport interface Ws {\n root: string;\n dir: string;\n projectJson: string;\n contextDir: string;\n facts: string;\n commands: string;\n decisionsDir: string;\n tasksActive: string;\n tasksCompleted: string;\n sessionsDir: string;\n analysisDir: string;\n discovery: string;\n onboardingPrompt: string;\n refreshPrompt: string;\n stateDir: string;\n analysisState: string;\n localDir: string;\n integrationsState: string;\n backupsDir: string;\n gitignore: string;\n}\n\nexport function wsPaths(root: string): Ws {\n const dir = path.join(root, WORKSPACE_DIR);\n const j = (...p: string[]): string => path.join(dir, ...p);\n return {\n root,\n dir,\n projectJson: j('project.json'),\n contextDir: j('context'),\n facts: j('context', 'facts.json'),\n commands: j('context', 'commands.json'),\n decisionsDir: j('decisions'),\n tasksActive: j('tasks', 'active'),\n tasksCompleted: j('tasks', 'completed'),\n sessionsDir: j('sessions'),\n analysisDir: j('analysis'),\n discovery: j('analysis', 'discovery.json'),\n onboardingPrompt: j('analysis', 'onboarding-prompt.md'),\n refreshPrompt: j('analysis', 'refresh-prompt.md'),\n stateDir: j('state'),\n analysisState: j('state', 'analysis.json'),\n localDir: j('local'),\n integrationsState: j('state', 'integrations.json'),\n backupsDir: j('local', 'backups'),\n gitignore: j('.gitignore'),\n };\n}\n\n/** Walk upward from start looking for an initialised workspace. The workspace location is never stored in it. */\nexport async function findWorkspaceRoot(start: string): Promise<string | null> {\n let dir = path.resolve(start);\n for (;;) {\n if (await pathExists(path.join(dir, WORKSPACE_DIR, 'project.json'))) return dir;\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\nexport async function requireWorkspace(cwd: string): Promise<Ws> {\n const root = await findWorkspaceRoot(cwd);\n if (!root) {\n throw new DevWorkspaceError('NOT_INITIALIZED', 'No DevWorkspace found in this directory or any parent.', {\n hint: 'Run \"devworkspace init\" in the project root.',\n });\n }\n return wsPaths(root);\n}\n\nexport async function loadProject(ws: Ws, migrations: Migration[] = MIGRATIONS.project ?? []): Promise<ProjectConfig> {\n const raw = await readJsonFile(ws.projectJson);\n const { data } = migrateData(raw, SCHEMA_VERSION, migrations, 'project.json');\n return validateProject(data);\n}\n\nexport async function saveProject(ws: Ws, cfg: ProjectConfig): Promise<void> {\n validateProject(cfg);\n await backupIfVersionChanges(ws, ws.projectJson, cfg.schemaVersion);\n await atomicWriteJson(ws.projectJson, cfg);\n}\n\n/** Before a write that upgrades a file's schemaVersion on disk, keep a copy of the old file. */\nexport async function backupIfVersionChanges(ws: Ws, file: string, newVersion: number, stamp?: string): Promise<string | null> {\n if (!(await pathExists(file))) return null;\n let onDisk: unknown;\n try {\n onDisk = ((await readJsonFile(file)) as { schemaVersion?: unknown }).schemaVersion;\n } catch {\n onDisk = undefined;\n }\n if (onDisk === newVersion) return null;\n const folder = stamp ?? new Date().toISOString().replace(/[:.]/g, '-');\n const dest = path.join(ws.backupsDir, folder, path.relative(ws.dir, file));\n await ensureDir(path.dirname(dest));\n await fs.copyFile(file, dest);\n return dest;\n}\n\nexport const LOCAL_GITIGNORE = [\n '# Managed by DevWorkspace. Machine-local state and regenerable output.',\n 'local/',\n 'analysis/onboarding-prompt.md',\n 'analysis/refresh-prompt.md',\n 'state/integrations.json',\n '*.tmp',\n '',\n].join('\\n');\n"]}
1
+ {"version":3,"file":"workspace.js","sourceRoot":"","sources":["../../src/core/workspace.ts"],"names":[],"mappings":"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","sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { DevWorkspaceError } from '../errors/index.js';\nimport { atomicWriteJson, ensureDir, pathExists, readJsonFile } from '../utils/fs.js';\nimport { validateProject, type ProjectConfig, SCHEMA_VERSION } from '../config/schemas.js';\nimport { MIGRATIONS, migrateData, type Migration } from './migrations.js';\n\nexport const WORKSPACE_DIR = '.devworkspace';\n\nexport interface Ws {\n root: string;\n dir: string;\n projectJson: string;\n contextDir: string;\n facts: string;\n commands: string;\n features: string;\n decisionsDir: string;\n tasksActive: string;\n tasksCompleted: string;\n sessionsDir: string;\n analysisDir: string;\n discovery: string;\n onboardingPrompt: string;\n refreshPrompt: string;\n stateDir: string;\n analysisState: string;\n localDir: string;\n integrationsState: string;\n backupsDir: string;\n gitignore: string;\n}\n\nexport function wsPaths(root: string): Ws {\n const dir = path.join(root, WORKSPACE_DIR);\n const j = (...p: string[]): string => path.join(dir, ...p);\n return {\n root,\n dir,\n projectJson: j('project.json'),\n contextDir: j('context'),\n facts: j('context', 'facts.json'),\n commands: j('context', 'commands.json'),\n features: j('context', 'features.json'),\n decisionsDir: j('decisions'),\n tasksActive: j('tasks', 'active'),\n tasksCompleted: j('tasks', 'completed'),\n sessionsDir: j('sessions'),\n analysisDir: j('analysis'),\n discovery: j('analysis', 'discovery.json'),\n onboardingPrompt: j('analysis', 'onboarding-prompt.md'),\n refreshPrompt: j('analysis', 'refresh-prompt.md'),\n stateDir: j('state'),\n analysisState: j('state', 'analysis.json'),\n localDir: j('local'),\n integrationsState: j('state', 'integrations.json'),\n backupsDir: j('local', 'backups'),\n gitignore: j('.gitignore'),\n };\n}\n\n/** Walk upward from start looking for an initialised workspace. The workspace location is never stored in it. */\nexport async function findWorkspaceRoot(start: string): Promise<string | null> {\n let dir = path.resolve(start);\n for (;;) {\n if (await pathExists(path.join(dir, WORKSPACE_DIR, 'project.json'))) return dir;\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\nexport async function requireWorkspace(cwd: string): Promise<Ws> {\n const root = await findWorkspaceRoot(cwd);\n if (!root) {\n throw new DevWorkspaceError('NOT_INITIALIZED', 'No DevWorkspace found in this directory or any parent.', {\n hint: 'Run \"devworkspace init\" in the project root.',\n });\n }\n return wsPaths(root);\n}\n\nexport async function loadProject(ws: Ws, migrations: Migration[] = MIGRATIONS.project ?? []): Promise<ProjectConfig> {\n const raw = await readJsonFile(ws.projectJson);\n const { data } = migrateData(raw, SCHEMA_VERSION, migrations, 'project.json');\n return validateProject(data);\n}\n\nexport async function saveProject(ws: Ws, cfg: ProjectConfig): Promise<void> {\n validateProject(cfg);\n await backupIfVersionChanges(ws, ws.projectJson, cfg.schemaVersion);\n await atomicWriteJson(ws.projectJson, cfg);\n}\n\n/** Before a write that upgrades a file's schemaVersion on disk, keep a copy of the old file. */\nexport async function backupIfVersionChanges(ws: Ws, file: string, newVersion: number, stamp?: string): Promise<string | null> {\n if (!(await pathExists(file))) return null;\n let onDisk: unknown;\n try {\n onDisk = ((await readJsonFile(file)) as { schemaVersion?: unknown }).schemaVersion;\n } catch {\n onDisk = undefined;\n }\n if (onDisk === newVersion) return null;\n const folder = stamp ?? new Date().toISOString().replace(/[:.]/g, '-');\n const dest = path.join(ws.backupsDir, folder, path.relative(ws.dir, file));\n await ensureDir(path.dirname(dest));\n await fs.copyFile(file, dest);\n return dest;\n}\n\nexport const LOCAL_GITIGNORE = [\n '# Managed by DevWorkspace. Machine-local state and regenerable output.',\n 'local/',\n 'analysis/onboarding-prompt.md',\n 'analysis/refresh-prompt.md',\n 'state/integrations.json',\n '*.tmp',\n '',\n].join('\\n');\n"]}
@@ -41,3 +41,4 @@ export interface MergeResult {
41
41
  export declare function mergeContract(previous: CommandEntry[] | null, detected: CommandEntry[], opts?: {
42
42
  truncated?: boolean;
43
43
  }): MergeResult;
44
+ export { type ContractDriftType, type ContractDriftItem, type ContractDriftReport, type ContractDriftIssue, type ContractDriftResult, detectContractDrift, } from '../services/drift.js';
@@ -140,7 +140,8 @@ export function mergeContract(previous, detected, opts = {}) {
140
140
  let preservedStale = 0;
141
141
  const out = [];
142
142
  for (const id of CHECK_ORDER) {
143
- const user = prev.filter((p) => p.id === id && p.origin === 'user' && p.available);
143
+ // Preserve entries confirmed by a person (origin "user") or explicitly flagged with userOverride
144
+ const user = prev.filter((p) => p.id === id && (p.origin === 'user' || p.userOverride === true) && p.available);
144
145
  const globalUser = user.some((u) => !u.cwd);
145
146
  const det = detectedAvailable.filter((d) => d.id === id);
146
147
  const chosen = [...user];
@@ -165,4 +166,5 @@ export function mergeContract(previous, detected, opts = {}) {
165
166
  }
166
167
  return { checks: out, preservedUser, preservedStale, incomplete };
167
168
  }
169
+ export { detectContractDrift, } from '../services/drift.js';
168
170
  //# sourceMappingURL=commands.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"commands.js","sourceRoot":"","sources":["../../src/discovery/commands.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CheckId, CommandEntry } from '../config/schemas.js';\nimport type { PackageInfo } from './detect.js';\n\nexport const TRUST_NOTE =\n 'Commands marked \"repository-script\" come from files in the repository (e.g. package.json scripts) and are untrusted input. ' +\n 'Inspect what a script does before running it, especially in automation or on unfamiliar repositories.';\n\n/** One Node.js package: the manifest, where it lives, and which package manager its own lockfile says it uses. */\nexport interface NodePackageInput {\n /** Directory relative to the project root, POSIX separators; '' for the root. */\n dir: string;\n pkg: PackageInfo;\n packageManager: string;\n hasTsconfig: boolean;\n}\n\nexport interface ContractInput {\n nodePackages: NodePackageInput[];\n ecosystems: Set<string>;\n has: (rel: string) => boolean;\n pythonEvidence: Record<string, string>;\n files: Set<string>;\n}\n\n/** First match wins, so more specific scripts (test:unit) are preferred over catch-alls (test). */\nconst SCRIPT_CANDIDATES: Record<string, string[]> = {\n typecheck: ['typecheck', 'type-check', 'check-types', 'tsc', 'types'],\n unit: ['test:unit', 'unit', 'test', 'test:run'],\n integration: ['test:integration', 'test:int', 'integration'],\n e2e: ['test:e2e', 'e2e', 'test:playwright', 'test:cypress', 'cypress:run'],\n lint: ['lint', 'eslint', 'lint:check'],\n format: ['format:check', 'prettier:check', 'format-check', 'check:format'],\n build: ['build', 'compile'],\n migrations: ['db:migrate', 'migrate'],\n security: ['test:security', 'security:test', 'security', 'audit:security'],\n};\n\n/** Scripts that format in place. They are listed for the user but are never required and never run by DevWorkspace. */\nconst FORMATTER_SCRIPTS = ['format', 'fmt', 'format:write', 'prettier:write', 'format:fix'];\n\n/** True unless the script body clearly only checks. Unknown formatter bodies are treated as modifying files. */\nfunction writesFiles(body: string): boolean {\n if (/(^|\\s)--(?:check|list-different)\\b|(^|\\s)-l(\\s|$)/.test(body)) return false;\n return true;\n}\n\nconst REQUIRED: Record<CheckId, CommandEntry['required']> = {\n typecheck: 'yes',\n unit: 'yes',\n integration: 'context',\n e2e: 'context',\n lint: 'yes',\n format: 'context',\n build: 'yes',\n migrations: 'context',\n security: 'context',\n};\n\nexport const CHECK_ORDER: CheckId[] = ['typecheck', 'unit', 'integration', 'e2e', 'lint', 'format', 'build', 'migrations', 'security'];\n\nfunction scriptCommand(pm: string, script: string): string {\n if (script === 'test' && (pm === 'npm' || pm === 'pnpm' || pm === 'yarn' || pm === 'bun')) return `${pm} test`;\n if (pm === 'yarn') return `yarn ${script}`;\n if (pm === 'bun') return `bun run ${script}`;\n return `${pm} run ${script}`;\n}\n\n/** Placeholder scripts such as npm's default \"no test specified\" do not count as tests. */\nfunction isRealScript(body: string): boolean {\n return !/no test specified/i.test(body) && body.trim() !== '';\n}\n\nconst byCwd = (a: CommandEntry, b: CommandEntry): number => (a.cwd ?? '').localeCompare(b.cwd ?? '');\n\n/**\n * Detected verification commands. Node commands are produced per package and only for scripts that exist in that\n * package's own manifest. Every check that no package provides gets one unavailable entry so the contract always\n * lists all nine checks.\n */\nexport function buildContract(input: ContractInput): CommandEntry[] {\n const found: CommandEntry[] = [];\n const add = (id: CheckId, command: string, source: string, origin: CommandEntry['origin'], cwd?: string, mutates?: boolean): void => {\n if (!found.some((f) => f.id === id && (f.cwd ?? '') === (cwd ?? ''))) {\n found.push({ id, available: true, command, required: mutates ? 'no' : REQUIRED[id], source, origin, ...(cwd ? { cwd } : {}), ...(mutates !== undefined ? { mutates } : {}) });\n }\n };\n\n for (const np of input.nodePackages) {\n const manifest = np.dir ? `${np.dir}/package.json` : 'package.json';\n for (const [id, names] of Object.entries(SCRIPT_CANDIDATES)) {\n for (const name of names) {\n const body = np.pkg.scripts[name];\n if (body !== undefined && isRealScript(body)) {\n add(id as CheckId, scriptCommand(np.packageManager, name), `${manifest}#scripts.${name}`, 'repository-script', np.dir, id === 'format' ? /(^|\\s)(?:--write|--fix|-w)(\\s|$)/.test(body) : undefined);\n break;\n }\n }\n }\n // A package with only an in-place formatter (`format`) still gets a format entry, flagged as modifying files.\n if (!found.some((f) => f.id === 'format' && (f.cwd ?? '') === np.dir)) {\n const name = FORMATTER_SCRIPTS.find((n) => np.pkg.scripts[n] !== undefined && isRealScript(np.pkg.scripts[n]!));\n if (name) add('format', scriptCommand(np.packageManager, name), `${manifest}#scripts.${name}`, 'repository-script', np.dir, writesFiles(np.pkg.scripts[name]!));\n }\n if (np.pkg.scripts.typecheck === undefined && np.hasTsconfig && np.pkg.hasTypes && !found.some((f) => f.id === 'typecheck' && (f.cwd ?? '') === np.dir)) {\n add('typecheck', `${np.packageManager === 'pnpm' ? 'pnpm exec' : 'npx'} tsc --noEmit`, `${np.dir ? `${np.dir}/` : ''}tsconfig.json`, 'inferred', np.dir);\n }\n }\n\n if (input.ecosystems.has('python')) {\n const ev = input.pythonEvidence;\n const hasTestsDir = [...input.files].some((f) => /^tests?\\//.test(f) && f.endsWith('.py'));\n if (ev.pytest || hasTestsDir || input.has('pytest.ini')) add('unit', 'pytest', ev.pytest ?? 'tests/', 'inferred');\n if (ev.ruff) add('lint', 'ruff check .', ev.ruff, 'inferred');\n if (ev.mypy) add('typecheck', 'mypy .', ev.mypy, 'inferred');\n if (ev.black) add('format', 'black --check .', ev.black, 'inferred');\n if (ev.alembic) add('migrations', 'alembic check', ev.alembic, 'inferred');\n }\n if (input.ecosystems.has('go')) {\n add('unit', 'go test ./...', 'go.mod', 'inferred');\n add('build', 'go build ./...', 'go.mod', 'inferred');\n add('lint', 'go vet ./...', 'go.mod', 'inferred');\n }\n if (input.ecosystems.has('rust')) {\n add('unit', 'cargo test', 'Cargo.toml', 'inferred');\n add('build', 'cargo build', 'Cargo.toml', 'inferred');\n add('lint', 'cargo clippy', 'Cargo.toml', 'inferred');\n }\n if (input.has('pom.xml')) {\n add('unit', 'mvn test', 'pom.xml', 'inferred');\n add('build', 'mvn package', 'pom.xml', 'inferred');\n }\n if (input.has('build.gradle') || input.has('build.gradle.kts')) {\n add('unit', 'gradle test', 'build.gradle', 'inferred');\n add('build', 'gradle build', 'build.gradle', 'inferred');\n }\n\n return withPlaceholders(found);\n}\n\nfunction withPlaceholders(entries: CommandEntry[]): CommandEntry[] {\n const out: CommandEntry[] = [];\n for (const id of CHECK_ORDER) {\n const mine = entries.filter((e) => e.id === id && e.available).sort(byCwd);\n if (mine.length > 0) out.push(...mine);\n else {\n const unavailable = entries.find((e) => e.id === id);\n out.push(unavailable ?? { id, available: false, command: null, required: 'no', source: null, origin: 'inferred' });\n }\n }\n return out;\n}\n\nexport interface MergeResult {\n checks: CommandEntry[];\n /** Entries kept from the previous contract because a human confirmed them (origin \"user\"). */\n preservedUser: number;\n /** Previously detected entries kept because this analysis found nothing or was cut short. */\n preservedStale: number;\n /** The new detection found no verification commands at all, or the scan was truncated. */\n incomplete: boolean;\n}\n\n/**\n * Combine the previous contract with fresh detection.\n * - Entries confirmed by a person (origin \"user\") are never dropped or overwritten. A user entry without a cwd\n * covers its whole check; one with a cwd covers only that directory, and detection fills in the other packages.\n * - If detection is incomplete, previously available entries survive too, with their original provenance.\n */\nexport function mergeContract(previous: CommandEntry[] | null, detected: CommandEntry[], opts: { truncated?: boolean } = {}): MergeResult {\n const prev = previous ?? [];\n const detectedAvailable = detected.filter((d) => d.available);\n const incomplete = detectedAvailable.length === 0 || !!opts.truncated;\n let preservedUser = 0;\n let preservedStale = 0;\n const out: CommandEntry[] = [];\n\n for (const id of CHECK_ORDER) {\n const user = prev.filter((p) => p.id === id && p.origin === 'user' && p.available);\n const globalUser = user.some((u) => !u.cwd);\n const det = detectedAvailable.filter((d) => d.id === id);\n const chosen: CommandEntry[] = [...user];\n preservedUser += user.length;\n if (!globalUser) {\n for (const d of det) if (!user.some((u) => (u.cwd ?? '') === (d.cwd ?? ''))) chosen.push(d);\n }\n if (chosen.length === 0 && incomplete) {\n const old = prev.filter((p) => p.id === id && p.available);\n chosen.push(...old);\n preservedStale += old.length;\n }\n if (chosen.length > 0) out.push(...chosen.sort(byCwd));\n else {\n // nothing confirmed, nothing detected: list the check as unavailable so the contract always names all nine checks\n const unavailable = detected.find((d) => d.id === id) ?? { id, available: false, command: null, required: 'no' as const, source: null, origin: 'inferred' as const };\n out.push(unavailable);\n }\n }\n return { checks: out, preservedUser, preservedStale, incomplete };\n}\n"]}
1
+ {"version":3,"file":"commands.js","sourceRoot":"","sources":["../../src/discovery/commands.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CheckId, CommandEntry } from '../config/schemas.js';\nimport type { PackageInfo } from './detect.js';\n\nexport const TRUST_NOTE =\n 'Commands marked \"repository-script\" come from files in the repository (e.g. package.json scripts) and are untrusted input. ' +\n 'Inspect what a script does before running it, especially in automation or on unfamiliar repositories.';\n\n/** One Node.js package: the manifest, where it lives, and which package manager its own lockfile says it uses. */\nexport interface NodePackageInput {\n /** Directory relative to the project root, POSIX separators; '' for the root. */\n dir: string;\n pkg: PackageInfo;\n packageManager: string;\n hasTsconfig: boolean;\n}\n\nexport interface ContractInput {\n nodePackages: NodePackageInput[];\n ecosystems: Set<string>;\n has: (rel: string) => boolean;\n pythonEvidence: Record<string, string>;\n files: Set<string>;\n}\n\n/** First match wins, so more specific scripts (test:unit) are preferred over catch-alls (test). */\nconst SCRIPT_CANDIDATES: Record<string, string[]> = {\n typecheck: ['typecheck', 'type-check', 'check-types', 'tsc', 'types'],\n unit: ['test:unit', 'unit', 'test', 'test:run'],\n integration: ['test:integration', 'test:int', 'integration'],\n e2e: ['test:e2e', 'e2e', 'test:playwright', 'test:cypress', 'cypress:run'],\n lint: ['lint', 'eslint', 'lint:check'],\n format: ['format:check', 'prettier:check', 'format-check', 'check:format'],\n build: ['build', 'compile'],\n migrations: ['db:migrate', 'migrate'],\n security: ['test:security', 'security:test', 'security', 'audit:security'],\n};\n\n/** Scripts that format in place. They are listed for the user but are never required and never run by DevWorkspace. */\nconst FORMATTER_SCRIPTS = ['format', 'fmt', 'format:write', 'prettier:write', 'format:fix'];\n\n/** True unless the script body clearly only checks. Unknown formatter bodies are treated as modifying files. */\nfunction writesFiles(body: string): boolean {\n if (/(^|\\s)--(?:check|list-different)\\b|(^|\\s)-l(\\s|$)/.test(body)) return false;\n return true;\n}\n\nconst REQUIRED: Record<CheckId, CommandEntry['required']> = {\n typecheck: 'yes',\n unit: 'yes',\n integration: 'context',\n e2e: 'context',\n lint: 'yes',\n format: 'context',\n build: 'yes',\n migrations: 'context',\n security: 'context',\n};\n\nexport const CHECK_ORDER: CheckId[] = ['typecheck', 'unit', 'integration', 'e2e', 'lint', 'format', 'build', 'migrations', 'security'];\n\nfunction scriptCommand(pm: string, script: string): string {\n if (script === 'test' && (pm === 'npm' || pm === 'pnpm' || pm === 'yarn' || pm === 'bun')) return `${pm} test`;\n if (pm === 'yarn') return `yarn ${script}`;\n if (pm === 'bun') return `bun run ${script}`;\n return `${pm} run ${script}`;\n}\n\n/** Placeholder scripts such as npm's default \"no test specified\" do not count as tests. */\nfunction isRealScript(body: string): boolean {\n return !/no test specified/i.test(body) && body.trim() !== '';\n}\n\nconst byCwd = (a: CommandEntry, b: CommandEntry): number => (a.cwd ?? '').localeCompare(b.cwd ?? '');\n\n/**\n * Detected verification commands. Node commands are produced per package and only for scripts that exist in that\n * package's own manifest. Every check that no package provides gets one unavailable entry so the contract always\n * lists all nine checks.\n */\nexport function buildContract(input: ContractInput): CommandEntry[] {\n const found: CommandEntry[] = [];\n const add = (id: CheckId, command: string, source: string, origin: CommandEntry['origin'], cwd?: string, mutates?: boolean): void => {\n if (!found.some((f) => f.id === id && (f.cwd ?? '') === (cwd ?? ''))) {\n found.push({ id, available: true, command, required: mutates ? 'no' : REQUIRED[id], source, origin, ...(cwd ? { cwd } : {}), ...(mutates !== undefined ? { mutates } : {}) });\n }\n };\n\n for (const np of input.nodePackages) {\n const manifest = np.dir ? `${np.dir}/package.json` : 'package.json';\n for (const [id, names] of Object.entries(SCRIPT_CANDIDATES)) {\n for (const name of names) {\n const body = np.pkg.scripts[name];\n if (body !== undefined && isRealScript(body)) {\n add(id as CheckId, scriptCommand(np.packageManager, name), `${manifest}#scripts.${name}`, 'repository-script', np.dir, id === 'format' ? /(^|\\s)(?:--write|--fix|-w)(\\s|$)/.test(body) : undefined);\n break;\n }\n }\n }\n // A package with only an in-place formatter (`format`) still gets a format entry, flagged as modifying files.\n if (!found.some((f) => f.id === 'format' && (f.cwd ?? '') === np.dir)) {\n const name = FORMATTER_SCRIPTS.find((n) => np.pkg.scripts[n] !== undefined && isRealScript(np.pkg.scripts[n]!));\n if (name) add('format', scriptCommand(np.packageManager, name), `${manifest}#scripts.${name}`, 'repository-script', np.dir, writesFiles(np.pkg.scripts[name]!));\n }\n if (np.pkg.scripts.typecheck === undefined && np.hasTsconfig && np.pkg.hasTypes && !found.some((f) => f.id === 'typecheck' && (f.cwd ?? '') === np.dir)) {\n add('typecheck', `${np.packageManager === 'pnpm' ? 'pnpm exec' : 'npx'} tsc --noEmit`, `${np.dir ? `${np.dir}/` : ''}tsconfig.json`, 'inferred', np.dir);\n }\n }\n\n if (input.ecosystems.has('python')) {\n const ev = input.pythonEvidence;\n const hasTestsDir = [...input.files].some((f) => /^tests?\\//.test(f) && f.endsWith('.py'));\n if (ev.pytest || hasTestsDir || input.has('pytest.ini')) add('unit', 'pytest', ev.pytest ?? 'tests/', 'inferred');\n if (ev.ruff) add('lint', 'ruff check .', ev.ruff, 'inferred');\n if (ev.mypy) add('typecheck', 'mypy .', ev.mypy, 'inferred');\n if (ev.black) add('format', 'black --check .', ev.black, 'inferred');\n if (ev.alembic) add('migrations', 'alembic check', ev.alembic, 'inferred');\n }\n if (input.ecosystems.has('go')) {\n add('unit', 'go test ./...', 'go.mod', 'inferred');\n add('build', 'go build ./...', 'go.mod', 'inferred');\n add('lint', 'go vet ./...', 'go.mod', 'inferred');\n }\n if (input.ecosystems.has('rust')) {\n add('unit', 'cargo test', 'Cargo.toml', 'inferred');\n add('build', 'cargo build', 'Cargo.toml', 'inferred');\n add('lint', 'cargo clippy', 'Cargo.toml', 'inferred');\n }\n if (input.has('pom.xml')) {\n add('unit', 'mvn test', 'pom.xml', 'inferred');\n add('build', 'mvn package', 'pom.xml', 'inferred');\n }\n if (input.has('build.gradle') || input.has('build.gradle.kts')) {\n add('unit', 'gradle test', 'build.gradle', 'inferred');\n add('build', 'gradle build', 'build.gradle', 'inferred');\n }\n\n return withPlaceholders(found);\n}\n\nfunction withPlaceholders(entries: CommandEntry[]): CommandEntry[] {\n const out: CommandEntry[] = [];\n for (const id of CHECK_ORDER) {\n const mine = entries.filter((e) => e.id === id && e.available).sort(byCwd);\n if (mine.length > 0) out.push(...mine);\n else {\n const unavailable = entries.find((e) => e.id === id);\n out.push(unavailable ?? { id, available: false, command: null, required: 'no', source: null, origin: 'inferred' });\n }\n }\n return out;\n}\n\nexport interface MergeResult {\n checks: CommandEntry[];\n /** Entries kept from the previous contract because a human confirmed them (origin \"user\"). */\n preservedUser: number;\n /** Previously detected entries kept because this analysis found nothing or was cut short. */\n preservedStale: number;\n /** The new detection found no verification commands at all, or the scan was truncated. */\n incomplete: boolean;\n}\n\n/**\n * Combine the previous contract with fresh detection.\n * - Entries confirmed by a person (origin \"user\") are never dropped or overwritten. A user entry without a cwd\n * covers its whole check; one with a cwd covers only that directory, and detection fills in the other packages.\n * - If detection is incomplete, previously available entries survive too, with their original provenance.\n */\nexport function mergeContract(previous: CommandEntry[] | null, detected: CommandEntry[], opts: { truncated?: boolean } = {}): MergeResult {\n const prev = previous ?? [];\n const detectedAvailable = detected.filter((d) => d.available);\n const incomplete = detectedAvailable.length === 0 || !!opts.truncated;\n let preservedUser = 0;\n let preservedStale = 0;\n const out: CommandEntry[] = [];\n\n for (const id of CHECK_ORDER) {\n // Preserve entries confirmed by a person (origin \"user\") or explicitly flagged with userOverride\n const user = prev.filter((p) => p.id === id && (p.origin === 'user' || p.userOverride === true) && p.available);\n const globalUser = user.some((u) => !u.cwd);\n const det = detectedAvailable.filter((d) => d.id === id);\n const chosen: CommandEntry[] = [...user];\n preservedUser += user.length;\n if (!globalUser) {\n for (const d of det) if (!user.some((u) => (u.cwd ?? '') === (d.cwd ?? ''))) chosen.push(d);\n }\n if (chosen.length === 0 && incomplete) {\n const old = prev.filter((p) => p.id === id && p.available);\n chosen.push(...old);\n preservedStale += old.length;\n }\n if (chosen.length > 0) out.push(...chosen.sort(byCwd));\n else {\n // nothing confirmed, nothing detected: list the check as unavailable so the contract always names all nine checks\n const unavailable = detected.find((d) => d.id === id) ?? { id, available: false, command: null, required: 'no' as const, source: null, origin: 'inferred' as const };\n out.push(unavailable);\n }\n }\n return { checks: out, preservedUser, preservedStale, incomplete };\n}\n\nexport {\n type ContractDriftType,\n type ContractDriftItem,\n type ContractDriftReport,\n type ContractDriftIssue,\n type ContractDriftResult,\n detectContractDrift,\n} from '../services/drift.js';\n"]}
@@ -0,0 +1,3 @@
1
+ import { type FeaturesFile } from './types.js';
2
+ export declare const FEATURE_SCHEMA_VERSION = 1;
3
+ export declare function validateFeatures(v: unknown, file?: string): FeaturesFile;
@@ -0,0 +1,79 @@
1
+ import { DevWorkspaceError } from '../errors/index.js';
2
+ import { validateRelativePath } from '../utils/paths.js';
3
+ import { Check } from '../config/validate.js';
4
+ import { VERIFICATION_STATUSES } from './types.js';
5
+ export const FEATURE_SCHEMA_VERSION = 1;
6
+ function validateComponent(c, comp, index, label) {
7
+ const cc = c.at_(`${label}.${index}`);
8
+ const co = cc.obj(comp);
9
+ const p = cc.at_('path').str(co.path, { max: 240 });
10
+ const pathErr = validateRelativePath(p);
11
+ if (pathErr) {
12
+ throw new DevWorkspaceError('SCHEMA', `Invalid features.json: component path "${p}" must be relative (${pathErr}).`, {
13
+ hint: 'Feature component paths must be relative to the repository root without escaping (no ".." or absolute paths).',
14
+ });
15
+ }
16
+ const status = co.status !== undefined ? cc.at_('status').oneOf(co.status, VERIFICATION_STATUSES) : 'inferred';
17
+ const symbol = co.symbol !== undefined ? cc.at_('symbol').str(co.symbol, { max: 200 }) : undefined;
18
+ const detail = co.detail !== undefined ? cc.at_('detail').str(co.detail, { max: 1000 }) : undefined;
19
+ return { path: p, status, ...(symbol ? { symbol } : {}), ...(detail ? { detail } : {}) };
20
+ }
21
+ function validateComponentGroup(c, group, groupName, defaultKeys = []) {
22
+ if (group === undefined || group === null)
23
+ return undefined;
24
+ const go = c.at_(groupName).obj(group);
25
+ const out = {};
26
+ const allKeys = Array.from(new Set([...defaultKeys, ...Object.keys(go)]));
27
+ for (const k of allKeys) {
28
+ if (go[k] !== undefined) {
29
+ const arr = c.at_(`${groupName}.${k}`).arr(go[k], 100);
30
+ out[k] = arr.map((item, idx) => validateComponent(c, item, idx, `${groupName}.${k}`));
31
+ }
32
+ }
33
+ return out;
34
+ }
35
+ export function validateFeatures(v, file = 'features.json') {
36
+ const c = new Check(file);
37
+ const o = c.obj(v);
38
+ c.at_('schemaVersion').int(o.schemaVersion, 1);
39
+ const rawFeatures = c.at_('features').arr(o.features, 500);
40
+ const seenIds = new Set();
41
+ const features = [];
42
+ for (let i = 0; i < rawFeatures.length; i++) {
43
+ const fc = c.at_(`features.${i}`);
44
+ const fo = fc.obj(rawFeatures[i]);
45
+ const id = fc.at_('id').str(fo.id, { min: 2, max: 100 });
46
+ if (!/^[a-zA-Z0-9_.-]+$/.test(id)) {
47
+ throw new DevWorkspaceError('SCHEMA', `Invalid ${file}: features.${i}.id "${id}" must contain only letters, numbers, dots, dashes, or underscores.`);
48
+ }
49
+ if (seenIds.has(id.toLowerCase())) {
50
+ throw new DevWorkspaceError('SCHEMA', `Invalid ${file}: duplicate feature id "${id}" at index ${i}.`);
51
+ }
52
+ seenIds.add(id.toLowerCase());
53
+ const name = fc.at_('name').str(fo.name, { min: 1, max: 200 });
54
+ const description = fo.description !== undefined ? fc.at_('description').str(fo.description, { max: 2000 }) : undefined;
55
+ const status = fo.status !== undefined ? fc.at_('status').oneOf(fo.status, VERIFICATION_STATUSES) : 'inferred';
56
+ const origin = fo.origin !== undefined ? fc.at_('origin').oneOf(fo.origin, ['user', 'inferred']) : undefined;
57
+ const tags = fo.tags !== undefined ? fc.at_('tags').strArr(fo.tags, 50) : undefined;
58
+ const updatedAt = fo.updatedAt !== undefined ? fc.at_('updatedAt').iso(fo.updatedAt) : new Date().toISOString();
59
+ const frontend = validateComponentGroup(fc, fo.frontend, 'frontend', ['entryPoints', 'apiClients']);
60
+ const backend = validateComponentGroup(fc, fo.backend, 'backend', ['routes', 'controllers', 'services', 'repositories']);
61
+ const database = validateComponentGroup(fc, fo.database, 'database', ['models', 'migrations']);
62
+ const tests = validateComponentGroup(fc, fo.tests, 'tests', ['unit', 'integration', 'e2e']);
63
+ features.push({
64
+ id,
65
+ name,
66
+ ...(description ? { description } : {}),
67
+ status,
68
+ ...(origin ? { origin } : {}),
69
+ ...(frontend ? { frontend } : {}),
70
+ ...(backend ? { backend } : {}),
71
+ ...(database ? { database } : {}),
72
+ ...(tests ? { tests } : {}),
73
+ ...(tags ? { tags } : {}),
74
+ updatedAt,
75
+ });
76
+ }
77
+ return { schemaVersion: o.schemaVersion, features };
78
+ }
79
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/features/schema.ts"],"names":[],"mappings":"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","sourcesContent":["import { DevWorkspaceError } from '../errors/index.js';\nimport { validateRelativePath } from '../utils/paths.js';\nimport { Check } from '../config/validate.js';\nimport { VERIFICATION_STATUSES, type FeatureComponent, type FeatureItem, type FeaturesFile } from './types.js';\n\nexport const FEATURE_SCHEMA_VERSION = 1;\n\nfunction validateComponent(c: Check, comp: unknown, index: number, label: string): FeatureComponent {\n const cc = c.at_(`${label}.${index}`);\n const co = cc.obj(comp);\n const p = cc.at_('path').str(co.path, { max: 240 });\n const pathErr = validateRelativePath(p);\n if (pathErr) {\n throw new DevWorkspaceError('SCHEMA', `Invalid features.json: component path \"${p}\" must be relative (${pathErr}).`, {\n hint: 'Feature component paths must be relative to the repository root without escaping (no \"..\" or absolute paths).',\n });\n }\n const status = co.status !== undefined ? cc.at_('status').oneOf(co.status, VERIFICATION_STATUSES) : 'inferred';\n const symbol = co.symbol !== undefined ? cc.at_('symbol').str(co.symbol, { max: 200 }) : undefined;\n const detail = co.detail !== undefined ? cc.at_('detail').str(co.detail, { max: 1000 }) : undefined;\n\n return { path: p, status, ...(symbol ? { symbol } : {}), ...(detail ? { detail } : {}) };\n}\n\nfunction validateComponentGroup(c: Check, group: unknown, groupName: string, defaultKeys: string[] = []): Record<string, FeatureComponent[]> | undefined {\n if (group === undefined || group === null) return undefined;\n const go = c.at_(groupName).obj(group);\n const out: Record<string, FeatureComponent[]> = {};\n\n const allKeys = Array.from(new Set([...defaultKeys, ...Object.keys(go)]));\n for (const k of allKeys) {\n if (go[k] !== undefined) {\n const arr = c.at_(`${groupName}.${k}`).arr(go[k], 100);\n out[k] = arr.map((item, idx) => validateComponent(c, item, idx, `${groupName}.${k}`));\n }\n }\n return out;\n}\n\nexport function validateFeatures(v: unknown, file = 'features.json'): FeaturesFile {\n const c = new Check(file);\n const o = c.obj(v);\n c.at_('schemaVersion').int(o.schemaVersion, 1);\n const rawFeatures = c.at_('features').arr(o.features, 500);\n\n const seenIds = new Set<string>();\n const features: FeatureItem[] = [];\n\n for (let i = 0; i < rawFeatures.length; i++) {\n const fc = c.at_(`features.${i}`);\n const fo = fc.obj(rawFeatures[i]);\n const id = fc.at_('id').str(fo.id, { min: 2, max: 100 });\n\n if (!/^[a-zA-Z0-9_.-]+$/.test(id)) {\n throw new DevWorkspaceError('SCHEMA', `Invalid ${file}: features.${i}.id \"${id}\" must contain only letters, numbers, dots, dashes, or underscores.`);\n }\n\n if (seenIds.has(id.toLowerCase())) {\n throw new DevWorkspaceError('SCHEMA', `Invalid ${file}: duplicate feature id \"${id}\" at index ${i}.`);\n }\n seenIds.add(id.toLowerCase());\n\n const name = fc.at_('name').str(fo.name, { min: 1, max: 200 });\n const description = fo.description !== undefined ? fc.at_('description').str(fo.description, { max: 2000 }) : undefined;\n const status = fo.status !== undefined ? fc.at_('status').oneOf(fo.status, VERIFICATION_STATUSES) : 'inferred';\n const origin = fo.origin !== undefined ? fc.at_('origin').oneOf(fo.origin, ['user', 'inferred'] as const) : undefined;\n const tags = fo.tags !== undefined ? fc.at_('tags').strArr(fo.tags, 50) : undefined;\n const updatedAt = fo.updatedAt !== undefined ? fc.at_('updatedAt').iso(fo.updatedAt) : new Date().toISOString();\n\n const frontend = validateComponentGroup(fc, fo.frontend, 'frontend', ['entryPoints', 'apiClients']);\n const backend = validateComponentGroup(fc, fo.backend, 'backend', ['routes', 'controllers', 'services', 'repositories']);\n const database = validateComponentGroup(fc, fo.database, 'database', ['models', 'migrations']);\n const tests = validateComponentGroup(fc, fo.tests, 'tests', ['unit', 'integration', 'e2e']);\n\n features.push({\n id,\n name,\n ...(description ? { description } : {}),\n status,\n ...(origin ? { origin } : {}),\n ...(frontend ? { frontend } : {}),\n ...(backend ? { backend } : {}),\n ...(database ? { database } : {}),\n ...(tests ? { tests } : {}),\n ...(tags ? { tags } : {}),\n updatedAt,\n });\n }\n\n return { schemaVersion: o.schemaVersion as number, features };\n}\n\n"]}
@@ -0,0 +1,77 @@
1
+ export declare const VERIFICATION_STATUSES: readonly ["verified", "inferred", "unknown"];
2
+ export type VerificationStatus = (typeof VERIFICATION_STATUSES)[number];
3
+ export interface FeatureComponent {
4
+ /** Repository-relative file path (POSIX separators). */
5
+ path: string;
6
+ /** Optional named export, class, function, or route symbol identifier. */
7
+ symbol?: string;
8
+ /** Status: verified (exists on disk and symbol found), inferred (derived heuristic), unknown. */
9
+ status?: VerificationStatus;
10
+ /** Optional detail or provenance explaining how this component was found/verified. */
11
+ detail?: string;
12
+ }
13
+ export interface FeatureFrontend {
14
+ [key: string]: FeatureComponent[] | undefined;
15
+ entryPoints?: FeatureComponent[];
16
+ apiClients?: FeatureComponent[];
17
+ }
18
+ export interface FeatureBackend {
19
+ [key: string]: FeatureComponent[] | undefined;
20
+ routes?: FeatureComponent[];
21
+ controllers?: FeatureComponent[];
22
+ services?: FeatureComponent[];
23
+ repositories?: FeatureComponent[];
24
+ }
25
+ export interface FeatureDatabase {
26
+ [key: string]: FeatureComponent[] | undefined;
27
+ models?: FeatureComponent[];
28
+ migrations?: FeatureComponent[];
29
+ }
30
+ export interface FeatureTests {
31
+ [key: string]: FeatureComponent[] | undefined;
32
+ unit?: FeatureComponent[];
33
+ integration?: FeatureComponent[];
34
+ e2e?: FeatureComponent[];
35
+ }
36
+ export interface FeatureItem {
37
+ /** Alphanumeric dotted key, e.g. "auth.login", "sales.orders". */
38
+ id: string;
39
+ /** Human-readable title of the feature. */
40
+ name: string;
41
+ description?: string;
42
+ status?: VerificationStatus;
43
+ origin?: 'user' | 'inferred';
44
+ frontend?: FeatureFrontend;
45
+ backend?: FeatureBackend;
46
+ database?: FeatureDatabase;
47
+ tests?: FeatureTests;
48
+ tags?: string[];
49
+ updatedAt?: string;
50
+ }
51
+ export interface FeaturesFile {
52
+ schemaVersion: number;
53
+ features: FeatureItem[];
54
+ }
55
+ export interface FeatureComponentValidation {
56
+ category: string;
57
+ type: string;
58
+ component: FeatureComponent;
59
+ pathExists: boolean;
60
+ symbolFound: boolean | null;
61
+ issues: string[];
62
+ }
63
+ export interface FeatureValidationResult {
64
+ featureId: string;
65
+ name: string;
66
+ status: VerificationStatus;
67
+ components: FeatureComponentValidation[];
68
+ valid: boolean;
69
+ issues: string[];
70
+ }
71
+ export interface FeatureValidationReport {
72
+ total: number;
73
+ verified: number;
74
+ inferred: number;
75
+ unknown: number;
76
+ features: FeatureValidationResult[];
77
+ }
@@ -0,0 +1,2 @@
1
+ export const VERIFICATION_STATUSES = ['verified', 'inferred', 'unknown'];
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/features/types.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,UAAU,EAAE,UAAU,EAAE,SAAS,CAAU,CAAC","sourcesContent":["export const VERIFICATION_STATUSES = ['verified', 'inferred', 'unknown'] as const;\nexport type VerificationStatus = (typeof VERIFICATION_STATUSES)[number];\n\nexport interface FeatureComponent {\n /** Repository-relative file path (POSIX separators). */\n path: string;\n /** Optional named export, class, function, or route symbol identifier. */\n symbol?: string;\n /** Status: verified (exists on disk and symbol found), inferred (derived heuristic), unknown. */\n status?: VerificationStatus;\n /** Optional detail or provenance explaining how this component was found/verified. */\n detail?: string;\n}\n\nexport interface FeatureFrontend {\n [key: string]: FeatureComponent[] | undefined;\n entryPoints?: FeatureComponent[];\n apiClients?: FeatureComponent[];\n}\n\nexport interface FeatureBackend {\n [key: string]: FeatureComponent[] | undefined;\n routes?: FeatureComponent[];\n controllers?: FeatureComponent[];\n services?: FeatureComponent[];\n repositories?: FeatureComponent[];\n}\n\nexport interface FeatureDatabase {\n [key: string]: FeatureComponent[] | undefined;\n models?: FeatureComponent[];\n migrations?: FeatureComponent[];\n}\n\nexport interface FeatureTests {\n [key: string]: FeatureComponent[] | undefined;\n unit?: FeatureComponent[];\n integration?: FeatureComponent[];\n e2e?: FeatureComponent[];\n}\n\nexport interface FeatureItem {\n /** Alphanumeric dotted key, e.g. \"auth.login\", \"sales.orders\". */\n id: string;\n /** Human-readable title of the feature. */\n name: string;\n description?: string;\n status?: VerificationStatus;\n origin?: 'user' | 'inferred';\n frontend?: FeatureFrontend;\n backend?: FeatureBackend;\n database?: FeatureDatabase;\n tests?: FeatureTests;\n tags?: string[];\n updatedAt?: string;\n}\n\nexport interface FeaturesFile {\n schemaVersion: number;\n features: FeatureItem[];\n}\n\nexport interface FeatureComponentValidation {\n category: string;\n type: string;\n component: FeatureComponent;\n pathExists: boolean;\n symbolFound: boolean | null;\n issues: string[];\n}\n\nexport interface FeatureValidationResult {\n featureId: string;\n name: string;\n status: VerificationStatus;\n components: FeatureComponentValidation[];\n valid: boolean;\n issues: string[];\n}\n\nexport interface FeatureValidationReport {\n total: number;\n verified: number;\n inferred: number;\n unknown: number;\n features: FeatureValidationResult[];\n}\n"]}
@@ -0,0 +1,14 @@
1
+ import type { FeatureItem, FeatureValidationReport, FeatureValidationResult } from './types.js';
2
+ /**
3
+ * Searches for a symbol identifier in file text:
4
+ * Checks for word-boundary matches (function/class/variable/interface name) or quoted route strings.
5
+ */
6
+ export declare function findSymbolInText(text: string, symbol: string): boolean;
7
+ /**
8
+ * Validates a single feature's component paths and symbols against the physical repository.
9
+ */
10
+ export declare function validateFeature(root: string, feature: FeatureItem): Promise<FeatureValidationResult>;
11
+ /**
12
+ * Validates an entire collection of features against the repository on disk.
13
+ */
14
+ export declare function validateFeatureMap(root: string, features: FeatureItem[]): Promise<FeatureValidationReport>;
@@ -0,0 +1,124 @@
1
+ import path from 'node:path';
2
+ import { pathExists, readTextBounded } from '../utils/fs.js';
3
+ function escapeRegExp(s) {
4
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
5
+ }
6
+ /**
7
+ * Searches for a symbol identifier in file text:
8
+ * Checks for word-boundary matches (function/class/variable/interface name) or quoted route strings.
9
+ */
10
+ export function findSymbolInText(text, symbol) {
11
+ if (!symbol || !text)
12
+ return false;
13
+ const escaped = escapeRegExp(symbol);
14
+ // Match as a code identifier or as a quoted route/endpoint pattern
15
+ const re = new RegExp(`(?:\\b|["'\`])${escaped}(?:\\b|["'\`])`);
16
+ return re.test(text);
17
+ }
18
+ function extractComponents(feature) {
19
+ const out = [];
20
+ const addGroup = (category, group) => {
21
+ if (!group)
22
+ return;
23
+ for (const [type, comps] of Object.entries(group)) {
24
+ if (Array.isArray(comps)) {
25
+ for (const c of comps)
26
+ out.push({ category, type, component: c });
27
+ }
28
+ }
29
+ };
30
+ addGroup('frontend', feature.frontend);
31
+ addGroup('backend', feature.backend);
32
+ addGroup('database', feature.database);
33
+ addGroup('tests', feature.tests);
34
+ return out;
35
+ }
36
+ /**
37
+ * Validates a single feature's component paths and symbols against the physical repository.
38
+ */
39
+ export async function validateFeature(root, feature) {
40
+ const targets = extractComponents(feature);
41
+ const componentResults = [];
42
+ const featureIssues = [];
43
+ for (const t of targets) {
44
+ const comp = t.component;
45
+ const fullPath = path.join(root, ...comp.path.split('/'));
46
+ const exists = await pathExists(fullPath);
47
+ const compIssues = [];
48
+ let symbolFound = null;
49
+ if (!exists) {
50
+ compIssues.push(`File "${comp.path}" does not exist on disk`);
51
+ featureIssues.push(`${t.category}.${t.type}: File "${comp.path}" not found`);
52
+ }
53
+ else if (comp.symbol) {
54
+ try {
55
+ const text = await readTextBounded(fullPath, 1024 * 1024);
56
+ if (text) {
57
+ symbolFound = findSymbolInText(text, comp.symbol);
58
+ if (!symbolFound) {
59
+ compIssues.push(`Symbol "${comp.symbol}" was not found in "${comp.path}"`);
60
+ featureIssues.push(`${t.category}.${t.type}: Symbol "${comp.symbol}" not found in "${comp.path}"`);
61
+ }
62
+ }
63
+ else {
64
+ symbolFound = false;
65
+ compIssues.push(`Could not read file text for "${comp.path}"`);
66
+ }
67
+ }
68
+ catch {
69
+ symbolFound = false;
70
+ compIssues.push(`Failed to read "${comp.path}"`);
71
+ }
72
+ }
73
+ componentResults.push({
74
+ category: t.category,
75
+ type: t.type,
76
+ component: comp,
77
+ pathExists: exists,
78
+ symbolFound,
79
+ issues: compIssues,
80
+ });
81
+ }
82
+ const allComponentsValid = componentResults.length > 0 && componentResults.every((c) => c.pathExists && (c.symbolFound === null || c.symbolFound === true));
83
+ const someComponentsValid = componentResults.some((c) => c.pathExists);
84
+ let status = 'unknown';
85
+ if (allComponentsValid)
86
+ status = 'verified';
87
+ else if (someComponentsValid)
88
+ status = 'inferred';
89
+ return {
90
+ featureId: feature.id,
91
+ name: feature.name,
92
+ status,
93
+ components: componentResults,
94
+ valid: featureIssues.length === 0,
95
+ issues: featureIssues,
96
+ };
97
+ }
98
+ /**
99
+ * Validates an entire collection of features against the repository on disk.
100
+ */
101
+ export async function validateFeatureMap(root, features) {
102
+ const results = [];
103
+ let verified = 0;
104
+ let inferred = 0;
105
+ let unknown = 0;
106
+ for (const f of features) {
107
+ const res = await validateFeature(root, f);
108
+ results.push(res);
109
+ if (res.status === 'verified')
110
+ verified++;
111
+ else if (res.status === 'inferred')
112
+ inferred++;
113
+ else
114
+ unknown++;
115
+ }
116
+ return {
117
+ total: features.length,
118
+ verified,
119
+ inferred,
120
+ unknown,
121
+ features: results,
122
+ };
123
+ }
124
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/features/validate.ts"],"names":[],"mappings":"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","sourcesContent":["import path from 'node:path';\nimport { pathExists, readTextBounded } from '../utils/fs.js';\nimport type {\n FeatureComponent,\n FeatureComponentValidation,\n FeatureItem,\n FeatureValidationReport,\n FeatureValidationResult,\n VerificationStatus,\n} from './types.js';\n\nfunction escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Searches for a symbol identifier in file text:\n * Checks for word-boundary matches (function/class/variable/interface name) or quoted route strings.\n */\nexport function findSymbolInText(text: string, symbol: string): boolean {\n if (!symbol || !text) return false;\n const escaped = escapeRegExp(symbol);\n // Match as a code identifier or as a quoted route/endpoint pattern\n const re = new RegExp(`(?:\\\\b|[\"'\\`])${escaped}(?:\\\\b|[\"'\\`])`);\n return re.test(text);\n}\n\ninterface ComponentTarget {\n category: string;\n type: string;\n component: FeatureComponent;\n}\n\nfunction extractComponents(feature: FeatureItem): ComponentTarget[] {\n const out: ComponentTarget[] = [];\n const addGroup = (category: string, group?: Record<string, FeatureComponent[] | undefined>): void => {\n if (!group) return;\n for (const [type, comps] of Object.entries(group)) {\n if (Array.isArray(comps)) {\n for (const c of comps) out.push({ category, type, component: c });\n }\n }\n };\n\n addGroup('frontend', feature.frontend);\n addGroup('backend', feature.backend);\n addGroup('database', feature.database);\n addGroup('tests', feature.tests);\n\n return out;\n}\n\n/**\n * Validates a single feature's component paths and symbols against the physical repository.\n */\nexport async function validateFeature(root: string, feature: FeatureItem): Promise<FeatureValidationResult> {\n const targets = extractComponents(feature);\n const componentResults: FeatureComponentValidation[] = [];\n const featureIssues: string[] = [];\n\n for (const t of targets) {\n const comp = t.component;\n const fullPath = path.join(root, ...comp.path.split('/'));\n const exists = await pathExists(fullPath);\n const compIssues: string[] = [];\n let symbolFound: boolean | null = null;\n\n if (!exists) {\n compIssues.push(`File \"${comp.path}\" does not exist on disk`);\n featureIssues.push(`${t.category}.${t.type}: File \"${comp.path}\" not found`);\n } else if (comp.symbol) {\n try {\n const text = await readTextBounded(fullPath, 1024 * 1024);\n if (text) {\n symbolFound = findSymbolInText(text, comp.symbol);\n if (!symbolFound) {\n compIssues.push(`Symbol \"${comp.symbol}\" was not found in \"${comp.path}\"`);\n featureIssues.push(`${t.category}.${t.type}: Symbol \"${comp.symbol}\" not found in \"${comp.path}\"`);\n }\n } else {\n symbolFound = false;\n compIssues.push(`Could not read file text for \"${comp.path}\"`);\n }\n } catch {\n symbolFound = false;\n compIssues.push(`Failed to read \"${comp.path}\"`);\n }\n }\n\n componentResults.push({\n category: t.category,\n type: t.type,\n component: comp,\n pathExists: exists,\n symbolFound,\n issues: compIssues,\n });\n }\n\n const allComponentsValid = componentResults.length > 0 && componentResults.every((c) => c.pathExists && (c.symbolFound === null || c.symbolFound === true));\n const someComponentsValid = componentResults.some((c) => c.pathExists);\n\n let status: VerificationStatus = 'unknown';\n if (allComponentsValid) status = 'verified';\n else if (someComponentsValid) status = 'inferred';\n\n return {\n featureId: feature.id,\n name: feature.name,\n status,\n components: componentResults,\n valid: featureIssues.length === 0,\n issues: featureIssues,\n };\n}\n\n/**\n * Validates an entire collection of features against the repository on disk.\n */\nexport async function validateFeatureMap(root: string, features: FeatureItem[]): Promise<FeatureValidationReport> {\n const results: FeatureValidationResult[] = [];\n let verified = 0;\n let inferred = 0;\n let unknown = 0;\n\n for (const f of features) {\n const res = await validateFeature(root, f);\n results.push(res);\n if (res.status === 'verified') verified++;\n else if (res.status === 'inferred') inferred++;\n else unknown++;\n }\n\n return {\n total: features.length,\n verified,\n inferred,\n unknown,\n features: results,\n };\n}\n\n"]}
package/dist/index.d.ts CHANGED
@@ -1,15 +1,18 @@
1
1
  export { packageVersion } from './core/version.js';
2
2
  export { DevWorkspaceError, EXIT } from './errors/index.js';
3
3
  export { initWorkspace } from './services/init.js';
4
+ export { setupProject, detectCliProvenance } from './services/setup.js';
4
5
  export { analyzeWorkspace } from './services/analysis.js';
5
6
  export { getStatus } from './services/status.js';
6
7
  export { runDoctor } from './services/doctor.js';
7
8
  export { refreshWorkspace } from './services/refresh.js';
8
9
  export { getTaskContext } from './services/taskContext.js';
9
10
  export { getContext, addFact, verifyFactByKey, removeFactByKey } from './services/knowledge.js';
11
+ export { listFeaturesService, getFeatureService, validateFeatureMapService, saveFeatureService } from './services/features.js';
12
+ export { detectContractDrift } from './discovery/commands.js';
10
13
  export { createTask, listTasksService, showTask, setTaskStatus, updateTask, recordVerification, recordTests, attestTask } from './services/tasks.js';
11
14
  export { recordDecision, getDecisions, recordSession, getSessions } from './services/records.js';
12
- export { installAgent } from './services/agent.js';
15
+ export { installAgent, listAgents, AGENTS, getAgentAdapter, listAgentAdapters, detectAllAgentFiles } from './services/agent.js';
13
16
  export { exportWorkspace, importWorkspace } from './services/portability.js';
14
17
  export { migrateWorkspace } from './services/migrate.js';
15
18
  export { resetWorkspace } from './services/reset.js';
@@ -25,5 +28,8 @@ export type { StatusResult } from './services/status.js';
25
28
  export type { DoctorResult, DoctorCheck } from './services/doctor.js';
26
29
  export type { Task, TaskStatus, TaskType } from './tasks/task.js';
27
30
  export type { Fact, ProjectConfig, CommandEntry } from './config/schemas.js';
31
+ export type { FeatureItem, FeatureComponent, FeaturesFile, FeatureValidationReport } from './features/types.js';
32
+ export type { ProjectSetupResult, CliProvenance, CliExecutionMode } from './services/setup.js';
28
33
  export type { IntegrationReport, StatusCode as IntegrationStatusCode } from './integrations/types.js';
29
- export type { AgentAdapter, PlannedChange } from './agents/types.js';
34
+ export type { AgentAdapter, AgentCapabilities, PlannedChange } from './agents/types.js';
35
+ export type { AgentInfo, AgentInstallResult } from './services/agent.js';
package/dist/index.js CHANGED
@@ -2,15 +2,18 @@
2
2
  export { packageVersion } from './core/version.js';
3
3
  export { DevWorkspaceError, EXIT } from './errors/index.js';
4
4
  export { initWorkspace } from './services/init.js';
5
+ export { setupProject, detectCliProvenance } from './services/setup.js';
5
6
  export { analyzeWorkspace } from './services/analysis.js';
6
7
  export { getStatus } from './services/status.js';
7
8
  export { runDoctor } from './services/doctor.js';
8
9
  export { refreshWorkspace } from './services/refresh.js';
9
10
  export { getTaskContext } from './services/taskContext.js';
10
11
  export { getContext, addFact, verifyFactByKey, removeFactByKey } from './services/knowledge.js';
12
+ export { listFeaturesService, getFeatureService, validateFeatureMapService, saveFeatureService } from './services/features.js';
13
+ export { detectContractDrift } from './discovery/commands.js';
11
14
  export { createTask, listTasksService, showTask, setTaskStatus, updateTask, recordVerification, recordTests, attestTask } from './services/tasks.js';
12
15
  export { recordDecision, getDecisions, recordSession, getSessions } from './services/records.js';
13
- export { installAgent } from './services/agent.js';
16
+ export { installAgent, listAgents, AGENTS, getAgentAdapter, listAgentAdapters, detectAllAgentFiles } from './services/agent.js';
14
17
  export { exportWorkspace, importWorkspace } from './services/portability.js';
15
18
  export { migrateWorkspace } from './services/migrate.js';
16
19
  export { resetWorkspace } from './services/reset.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,oHAAoH;AACpH,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAChG,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,QAAQ,EAAE,aAAa,EAAE,UAAU,EAAE,kBAAkB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACrJ,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACjG,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACnF,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACnF,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3E,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC","sourcesContent":["// Programmatic API: the same engine that powers the CLI, for a future VS Code extension, dashboards and automation.\nexport { packageVersion } from './core/version.js';\nexport { DevWorkspaceError, EXIT } from './errors/index.js';\nexport { initWorkspace } from './services/init.js';\nexport { analyzeWorkspace } from './services/analysis.js';\nexport { getStatus } from './services/status.js';\nexport { runDoctor } from './services/doctor.js';\nexport { refreshWorkspace } from './services/refresh.js';\nexport { getTaskContext } from './services/taskContext.js';\nexport { getContext, addFact, verifyFactByKey, removeFactByKey } from './services/knowledge.js';\nexport { createTask, listTasksService, showTask, setTaskStatus, updateTask, recordVerification, recordTests, attestTask } from './services/tasks.js';\nexport { recordDecision, getDecisions, recordSession, getSessions } from './services/records.js';\nexport { installAgent } from './services/agent.js';\nexport { exportWorkspace, importWorkspace } from './services/portability.js';\nexport { migrateWorkspace } from './services/migrate.js';\nexport { resetWorkspace } from './services/reset.js';\nexport { setupIntegrations, integrationsStatus } from './services/integrations.js';\nexport { classifyRequest, detectConstraints, WORKFLOWS } from './tasks/routing.js';\nexport { evaluateGate } from './tasks/task.js';\nexport { redact, findSecrets, containsSecret } from './security/redact.js';\nexport { isSensitiveFile } from './security/files.js';\nexport { buildOnboardingPrompt } from './prompts/onboarding.js';\nexport { ONBOARDING_REQUIREMENTS, REFRESH_REQUIREMENTS, validatePrompt } from './prompts/requirements.js';\nexport type { ServiceOptions } from './services/common.js';\nexport type { StatusResult } from './services/status.js';\nexport type { DoctorResult, DoctorCheck } from './services/doctor.js';\nexport type { Task, TaskStatus, TaskType } from './tasks/task.js';\nexport type { Fact, ProjectConfig, CommandEntry } from './config/schemas.js';\nexport type { IntegrationReport, StatusCode as IntegrationStatusCode } from './integrations/types.js';\nexport type { AgentAdapter, PlannedChange } from './agents/types.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,oHAAoH;AACpH,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AACxE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAChG,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC/H,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,QAAQ,EAAE,aAAa,EAAE,UAAU,EAAE,kBAAkB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACrJ,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACjG,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,eAAe,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAChI,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACnF,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACnF,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3E,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC","sourcesContent":["// Programmatic API: the same engine that powers the CLI, for a future VS Code extension, dashboards and automation.\nexport { packageVersion } from './core/version.js';\nexport { DevWorkspaceError, EXIT } from './errors/index.js';\nexport { initWorkspace } from './services/init.js';\nexport { setupProject, detectCliProvenance } from './services/setup.js';\nexport { analyzeWorkspace } from './services/analysis.js';\nexport { getStatus } from './services/status.js';\nexport { runDoctor } from './services/doctor.js';\nexport { refreshWorkspace } from './services/refresh.js';\nexport { getTaskContext } from './services/taskContext.js';\nexport { getContext, addFact, verifyFactByKey, removeFactByKey } from './services/knowledge.js';\nexport { listFeaturesService, getFeatureService, validateFeatureMapService, saveFeatureService } from './services/features.js';\nexport { detectContractDrift } from './discovery/commands.js';\nexport { createTask, listTasksService, showTask, setTaskStatus, updateTask, recordVerification, recordTests, attestTask } from './services/tasks.js';\nexport { recordDecision, getDecisions, recordSession, getSessions } from './services/records.js';\nexport { installAgent, listAgents, AGENTS, getAgentAdapter, listAgentAdapters, detectAllAgentFiles } from './services/agent.js';\nexport { exportWorkspace, importWorkspace } from './services/portability.js';\nexport { migrateWorkspace } from './services/migrate.js';\nexport { resetWorkspace } from './services/reset.js';\nexport { setupIntegrations, integrationsStatus } from './services/integrations.js';\nexport { classifyRequest, detectConstraints, WORKFLOWS } from './tasks/routing.js';\nexport { evaluateGate } from './tasks/task.js';\nexport { redact, findSecrets, containsSecret } from './security/redact.js';\nexport { isSensitiveFile } from './security/files.js';\nexport { buildOnboardingPrompt } from './prompts/onboarding.js';\nexport { ONBOARDING_REQUIREMENTS, REFRESH_REQUIREMENTS, validatePrompt } from './prompts/requirements.js';\nexport type { ServiceOptions } from './services/common.js';\nexport type { StatusResult } from './services/status.js';\nexport type { DoctorResult, DoctorCheck } from './services/doctor.js';\nexport type { Task, TaskStatus, TaskType } from './tasks/task.js';\nexport type { Fact, ProjectConfig, CommandEntry } from './config/schemas.js';\nexport type { FeatureItem, FeatureComponent, FeaturesFile, FeatureValidationReport } from './features/types.js';\nexport type { ProjectSetupResult, CliProvenance, CliExecutionMode } from './services/setup.js';\nexport type { IntegrationReport, StatusCode as IntegrationStatusCode } from './integrations/types.js';\nexport type { AgentAdapter, AgentCapabilities, PlannedChange } from './agents/types.js';\nexport type { AgentInfo, AgentInstallResult } from './services/agent.js';\n"]}
@@ -18,6 +18,18 @@ export interface NativeSpec {
18
18
  pluginName: string;
19
19
  }
20
20
  export declare const pluginId: (s: NativeSpec) => string;
21
+ /**
22
+ * Parse `claude plugin list --json` output: a JSON array, single-line or pretty-printed. The whole output is parsed first;
23
+ * if a banner or warning precedes it, the text from the first line that starts the array is tried instead.
24
+ * Anything else is an error: "could not read" must never be mistaken for "no plugins installed".
25
+ */
26
+ export declare function parsePluginList(stdout: string): {
27
+ ok: true;
28
+ plugins: InstalledPlugin[];
29
+ } | {
30
+ ok: false;
31
+ error: string;
32
+ };
21
33
  export declare function listPlugins(ctx: AdapterContext, claude: Tool): Promise<{
22
34
  ok: boolean;
23
35
  plugins: InstalledPlugin[];