devworkspace 0.2.2 → 0.3.1

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 (80) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +5 -4
  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/claude/content.js +1 -1
  7. package/dist/agents/claude/content.js.map +1 -1
  8. package/dist/agents/codex/adapter.d.ts +2 -0
  9. package/dist/agents/codex/adapter.js +61 -0
  10. package/dist/agents/codex/adapter.js.map +1 -0
  11. package/dist/agents/copilot/adapter.d.ts +2 -0
  12. package/dist/agents/copilot/adapter.js +62 -0
  13. package/dist/agents/copilot/adapter.js.map +1 -0
  14. package/dist/agents/cursor/adapter.d.ts +2 -0
  15. package/dist/agents/cursor/adapter.js +127 -0
  16. package/dist/agents/cursor/adapter.js.map +1 -0
  17. package/dist/agents/index.d.ts +15 -0
  18. package/dist/agents/index.js +54 -0
  19. package/dist/agents/index.js.map +1 -0
  20. package/dist/agents/shared.d.ts +20 -0
  21. package/dist/agents/shared.js +76 -0
  22. package/dist/agents/shared.js.map +1 -0
  23. package/dist/agents/types.d.ts +16 -1
  24. package/dist/agents/types.js.map +1 -1
  25. package/dist/cli/args.d.ts +3 -0
  26. package/dist/cli/args.js +1 -0
  27. package/dist/cli/args.js.map +1 -1
  28. package/dist/cli/help.js +15 -8
  29. package/dist/cli/help.js.map +1 -1
  30. package/dist/cli/render.d.ts +4 -1
  31. package/dist/cli/render.js +46 -0
  32. package/dist/cli/render.js.map +1 -1
  33. package/dist/cli/run.js +24 -6
  34. package/dist/cli/run.js.map +1 -1
  35. package/dist/discovery/cache.d.ts +19 -0
  36. package/dist/discovery/cache.js +50 -0
  37. package/dist/discovery/cache.js.map +1 -0
  38. package/dist/index.d.ts +4 -2
  39. package/dist/index.js +2 -1
  40. package/dist/index.js.map +1 -1
  41. package/dist/integrations/adapters/claudePlugin.d.ts +12 -0
  42. package/dist/integrations/adapters/claudePlugin.js +48 -10
  43. package/dist/integrations/adapters/claudePlugin.js.map +1 -1
  44. package/dist/integrations/adapters/pluginSkill.js +5 -0
  45. package/dist/integrations/adapters/pluginSkill.js.map +1 -1
  46. package/dist/security/files.js +10 -2
  47. package/dist/security/files.js.map +1 -1
  48. package/dist/security/redact.js +1 -1
  49. package/dist/security/redact.js.map +1 -1
  50. package/dist/services/agent.d.ts +12 -2
  51. package/dist/services/agent.js +96 -14
  52. package/dist/services/agent.js.map +1 -1
  53. package/dist/services/analysis.d.ts +7 -1
  54. package/dist/services/analysis.js +14 -7
  55. package/dist/services/analysis.js.map +1 -1
  56. package/dist/services/doctor.js +31 -14
  57. package/dist/services/doctor.js.map +1 -1
  58. package/dist/services/refresh.js +1 -1
  59. package/dist/services/refresh.js.map +1 -1
  60. package/dist/services/reset.js +45 -5
  61. package/dist/services/reset.js.map +1 -1
  62. package/dist/services/status.d.ts +2 -0
  63. package/dist/services/status.js +62 -10
  64. package/dist/services/status.js.map +1 -1
  65. package/dist/services/taskContext.d.ts +59 -0
  66. package/dist/services/taskContext.js +164 -0
  67. package/dist/services/taskContext.js.map +1 -0
  68. package/dist/services/tasks.js +6 -1
  69. package/dist/services/tasks.js.map +1 -1
  70. package/dist/tasks/routing.js +16 -10
  71. package/dist/tasks/routing.js.map +1 -1
  72. package/dist/tasks/task.js +6 -2
  73. package/dist/tasks/task.js.map +1 -1
  74. package/dist/verification/scope.js +1 -1
  75. package/dist/verification/scope.js.map +1 -1
  76. package/docs/agents.md +60 -0
  77. package/docs/architecture.md +2 -2
  78. package/docs/benchmarking.md +19 -0
  79. package/docs/knowledge.md +17 -0
  80. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"scope.js","sourceRoot":"","sources":["../../src/verification/scope.ts"],"names":[],"mappings":"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","sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport type { CommandEntry } from '../config/schemas.js';\nimport { gitAvailable, parsePorcelainZ } from '../git/git.js';\nimport { isSensitiveFile } from '../security/files.js';\nimport type { TaskScope } from '../tasks/task.js';\nimport { sha256File, sha256Text } from '../utils/fs.js';\nimport { run } from '../utils/proc.js';\n\nconst MAX_TRACKED_FILES = 2000;\nconst MAX_HASH_BYTES = 8 * 1024 * 1024;\n/** Documentation and tool-managed folders never decide which package's checks apply. */\nconst NOT_CODE = /^(?:\\.devworkspace|\\.claude|graphify-out)\\/|\\.(?:md|mdx|rst|txt)$/i;\n\nexport type ChangeMap = Record<string, string>;\n\nasync function git(root: string, args: string[]): Promise<string | null> {\n const r = await run('git', args, { cwd: root, timeoutMs: 30_000 });\n return r.ok ? r.stdout : null;\n}\n\nasync function fileFingerprint(root: string, rel: string): Promise<string> {\n const full = path.join(root, ...rel.split('/'));\n try {\n const st = await fs.lstat(full);\n if (!st.isFile()) return 'not-a-file';\n if (st.size > MAX_HASH_BYTES) return `large:${st.size}:${Math.floor(st.mtimeMs)}`;\n return await sha256File(full);\n } catch {\n return 'deleted';\n }\n}\n\n/** Files that differ from HEAD right now (modified, staged or untracked), project-relative, with a content hash. */\nexport async function dirtyFiles(root: string): Promise<{ head: string | null; files: ChangeMap } | null> {\n if (!(await gitAvailable())) return null;\n if ((await git(root, ['rev-parse', '--is-inside-work-tree']))?.trim() !== 'true') return null;\n const prefix = ((await git(root, ['rev-parse', '--show-prefix'])) ?? '').trim();\n const head = ((await git(root, ['rev-parse', 'HEAD'])) ?? '').trim() || null;\n const status = await git(root, ['status', '--porcelain=v1', '-z', '--untracked-files=all']);\n if (status === null) return null;\n const files: ChangeMap = {};\n for (const e of parsePorcelainZ(status)) {\n if (e.xy === '!!' || !e.path.startsWith(prefix)) continue;\n const rel = e.path.slice(prefix.length);\n if (NOT_CODE.test(rel) || isSensitiveFile(rel)) continue;\n if (Object.keys(files).length >= MAX_TRACKED_FILES) break;\n files[rel] = await fileFingerprint(root, rel);\n }\n return { head, files };\n}\n\n/** Snapshot taken when a task is created: what is already modified is not the task's doing. */\nexport async function captureScope(root: string, now: string): Promise<TaskScope> {\n const d = await dirtyFiles(root);\n return { capturedAt: now, head: d?.head ?? null, baseline: d?.files ?? {} };\n}\n\n/** Project-relative files committed since `head` (so committing during a task does not hide its changes). */\nasync function committedSince(root: string, head: string): Promise<string[] | null> {\n const prefix = ((await git(root, ['rev-parse', '--show-prefix'])) ?? '').trim();\n const out = await git(root, ['diff', '--name-only', '-z', head, 'HEAD']);\n if (out === null) return null;\n return out.split('\\0').filter((f) => f && f.startsWith(prefix)).map((f) => f.slice(prefix.length));\n}\n\n/** Files that changed since the task started, with their current hashes. Null when that cannot be determined. */\nexport async function changedSince(root: string, scope: TaskScope): Promise<ChangeMap | null> {\n const now = await dirtyFiles(root);\n if (!now) return null;\n const changed: ChangeMap = {};\n for (const [rel, hash] of Object.entries(now.files)) if (scope.baseline[rel] !== hash) changed[rel] = hash;\n // A file that was modified at the start and has since been cleaned up (committed or reverted) also changed.\n for (const rel of Object.keys(scope.baseline)) if (!(rel in now.files)) changed[rel] = await fileFingerprint(root, rel);\n if (scope.head && now.head && scope.head !== now.head) {\n const committed = await committedSince(root, scope.head);\n if (committed === null) return null;\n for (const rel of committed) if (!NOT_CODE.test(rel) && !isSensitiveFile(rel) && !(rel in changed)) changed[rel] = await fileFingerprint(root, rel);\n }\n return changed;\n}\n\n/** Directories (relative to the project root) that have their own checks in the contract, longest first. */\nexport function packageDirs(contract: CommandEntry[]): string[] {\n const dirs = new Set<string>();\n for (const c of contract) if (c.available && c.cwd && c.cwd !== '.') dirs.add(c.cwd);\n return [...dirs].sort((a, b) => b.length - a.length || a.localeCompare(b));\n}\n\n/**\n * Fingerprint of the changes inside each package. A change under a package belongs to it; a change outside every package\n * (root config, shared tooling) belongs to all of them. \".\" covers everything. Returns null when nothing is known.\n */\nexport function scopeByPackage(changes: ChangeMap | null, contract: CommandEntry[]): Record<string, string> | null {\n if (!changes) return null;\n const files = Object.keys(changes).filter((f) => !NOT_CODE.test(f)).sort();\n if (files.length === 0) return null;\n const dirs = packageDirs(contract);\n const owner = (f: string): string | null => dirs.find((d) => f === d || f.startsWith(`${d}/`)) ?? null;\n const shared = files.filter((f) => owner(f) === null);\n const line = (f: string): string => `${f}:${changes[f]}`;\n const out: Record<string, string> = { '.': sha256Text(files.map(line).join('\\n')) };\n for (const d of dirs) {\n const mine = files.filter((f) => owner(f) === d);\n if (mine.length > 0 || shared.length > 0) out[d] = sha256Text([...mine, ...shared].sort().map(line).join('\\n'));\n }\n return out;\n}\n"]}
1
+ {"version":3,"file":"scope.js","sourceRoot":"","sources":["../../src/verification/scope.ts"],"names":[],"mappings":"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","sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport type { CommandEntry } from '../config/schemas.js';\nimport { gitAvailable, parsePorcelainZ } from '../git/git.js';\nimport { isSensitiveFile } from '../security/files.js';\nimport type { TaskScope } from '../tasks/task.js';\nimport { sha256File, sha256Text } from '../utils/fs.js';\nimport { run } from '../utils/proc.js';\n\nconst MAX_TRACKED_FILES = 2000;\nconst MAX_HASH_BYTES = 8 * 1024 * 1024;\n/** Documentation and tool-managed folders never decide which package's checks apply. */\nconst NOT_CODE = /^(?:\\.devworkspace|\\.claude|\\.cursor|\\.github|graphify-out)\\/|\\.(?:md|mdx|mdc|rst|txt)$/i;\n\nexport type ChangeMap = Record<string, string>;\n\nasync function git(root: string, args: string[]): Promise<string | null> {\n const r = await run('git', args, { cwd: root, timeoutMs: 30_000 });\n return r.ok ? r.stdout : null;\n}\n\nasync function fileFingerprint(root: string, rel: string): Promise<string> {\n const full = path.join(root, ...rel.split('/'));\n try {\n const st = await fs.lstat(full);\n if (!st.isFile()) return 'not-a-file';\n if (st.size > MAX_HASH_BYTES) return `large:${st.size}:${Math.floor(st.mtimeMs)}`;\n return await sha256File(full);\n } catch {\n return 'deleted';\n }\n}\n\n/** Files that differ from HEAD right now (modified, staged or untracked), project-relative, with a content hash. */\nexport async function dirtyFiles(root: string): Promise<{ head: string | null; files: ChangeMap } | null> {\n if (!(await gitAvailable())) return null;\n if ((await git(root, ['rev-parse', '--is-inside-work-tree']))?.trim() !== 'true') return null;\n const prefix = ((await git(root, ['rev-parse', '--show-prefix'])) ?? '').trim();\n const head = ((await git(root, ['rev-parse', 'HEAD'])) ?? '').trim() || null;\n const status = await git(root, ['status', '--porcelain=v1', '-z', '--untracked-files=all']);\n if (status === null) return null;\n const files: ChangeMap = {};\n for (const e of parsePorcelainZ(status)) {\n if (e.xy === '!!' || !e.path.startsWith(prefix)) continue;\n const rel = e.path.slice(prefix.length);\n if (NOT_CODE.test(rel) || isSensitiveFile(rel)) continue;\n if (Object.keys(files).length >= MAX_TRACKED_FILES) break;\n files[rel] = await fileFingerprint(root, rel);\n }\n return { head, files };\n}\n\n/** Snapshot taken when a task is created: what is already modified is not the task's doing. */\nexport async function captureScope(root: string, now: string): Promise<TaskScope> {\n const d = await dirtyFiles(root);\n return { capturedAt: now, head: d?.head ?? null, baseline: d?.files ?? {} };\n}\n\n/** Project-relative files committed since `head` (so committing during a task does not hide its changes). */\nasync function committedSince(root: string, head: string): Promise<string[] | null> {\n const prefix = ((await git(root, ['rev-parse', '--show-prefix'])) ?? '').trim();\n const out = await git(root, ['diff', '--name-only', '-z', head, 'HEAD']);\n if (out === null) return null;\n return out.split('\\0').filter((f) => f && f.startsWith(prefix)).map((f) => f.slice(prefix.length));\n}\n\n/** Files that changed since the task started, with their current hashes. Null when that cannot be determined. */\nexport async function changedSince(root: string, scope: TaskScope): Promise<ChangeMap | null> {\n const now = await dirtyFiles(root);\n if (!now) return null;\n const changed: ChangeMap = {};\n for (const [rel, hash] of Object.entries(now.files)) if (scope.baseline[rel] !== hash) changed[rel] = hash;\n // A file that was modified at the start and has since been cleaned up (committed or reverted) also changed.\n for (const rel of Object.keys(scope.baseline)) if (!(rel in now.files)) changed[rel] = await fileFingerprint(root, rel);\n if (scope.head && now.head && scope.head !== now.head) {\n const committed = await committedSince(root, scope.head);\n if (committed === null) return null;\n for (const rel of committed) if (!NOT_CODE.test(rel) && !isSensitiveFile(rel) && !(rel in changed)) changed[rel] = await fileFingerprint(root, rel);\n }\n return changed;\n}\n\n/** Directories (relative to the project root) that have their own checks in the contract, longest first. */\nexport function packageDirs(contract: CommandEntry[]): string[] {\n const dirs = new Set<string>();\n for (const c of contract) if (c.available && c.cwd && c.cwd !== '.') dirs.add(c.cwd);\n return [...dirs].sort((a, b) => b.length - a.length || a.localeCompare(b));\n}\n\n/**\n * Fingerprint of the changes inside each package. A change under a package belongs to it; a change outside every package\n * (root config, shared tooling) belongs to all of them. \".\" covers everything. Returns null when nothing is known.\n */\nexport function scopeByPackage(changes: ChangeMap | null, contract: CommandEntry[]): Record<string, string> | null {\n if (!changes) return null;\n const files = Object.keys(changes).filter((f) => !NOT_CODE.test(f)).sort();\n if (files.length === 0) return null;\n const dirs = packageDirs(contract);\n const owner = (f: string): string | null => dirs.find((d) => f === d || f.startsWith(`${d}/`)) ?? null;\n const shared = files.filter((f) => owner(f) === null);\n const line = (f: string): string => `${f}:${changes[f]}`;\n const out: Record<string, string> = { '.': sha256Text(files.map(line).join('\\n')) };\n for (const d of dirs) {\n const mine = files.filter((f) => owner(f) === d);\n if (mine.length > 0 || shared.length > 0) out[d] = sha256Text([...mine, ...shared].sort().map(line).join('\\n'));\n }\n return out;\n}\n"]}
package/docs/agents.md ADDED
@@ -0,0 +1,60 @@
1
+ # Coding Agent Integrations
2
+
3
+ DevWorkspace is agent-agnostic. While it originated with Claude Code support, it provides typed adapters for multiple AI coding agents and tools across the ecosystem:
4
+
5
+ | Agent | Adapter ID | Primary Instruction Target | Capabilities |
6
+ | --- | --- | --- | --- |
7
+ | **Claude Code** | `claude` | `CLAUDE.md` or `.claude/CLAUDE.md`, `.claude/skills/devworkspace/SKILL.md` | Instructions, Skills, Plugins, CLI |
8
+ | **OpenAI Codex** | `codex` (alias `agents`) | `AGENTS.md` | Instructions |
9
+ | **Cursor** | `cursor` | `.cursor/rules/devworkspace.mdc` (or legacy `.cursorrules`) | Instructions |
10
+ | **GitHub Copilot** | `copilot` | `.github/copilot-instructions.md` | Instructions |
11
+
12
+ ## Installing Agent Instructions
13
+
14
+ Use the `devworkspace agent` CLI command to manage agent integrations:
15
+
16
+ ```bash
17
+ # Preview what files would change
18
+ devworkspace agent install --agent cursor --dry-run
19
+
20
+ # Install for a specific agent
21
+ devworkspace agent install --agent cursor
22
+ devworkspace agent install --agent copilot
23
+ devworkspace agent install --agent codex
24
+ devworkspace agent install --agent claude
25
+
26
+ # Install for all supported agents in one command
27
+ devworkspace agent install --agent all
28
+
29
+ # List supported and detected agent integrations
30
+ devworkspace agent list
31
+ ```
32
+
33
+ ## How Adapters Work
34
+
35
+ Every adapter implements the `AgentAdapter` contract defined in `src/agents/types.ts`:
36
+
37
+ - **Shared Section Engine (`src/agents/shared.ts`)**:
38
+ Instructions are injected within standardized HTML comment markers:
39
+ ```markdown
40
+ <!-- devworkspace:start (managed by DevWorkspace - edit outside this block; run "devworkspace agent install" to refresh) -->
41
+ ## DevWorkspace
42
+ ...
43
+ <!-- devworkspace:end -->
44
+ ```
45
+ User-authored content above or below the markers is strictly preserved byte-for-byte, including CRLF line endings on Windows.
46
+
47
+ - **Non-Destructive Reset (`devworkspace reset --include-agent-files`)**:
48
+ When resetting a workspace with `--include-agent-files`, DevWorkspace removes only its own managed blocks. If an instruction file contains user content outside the block, the user's content is preserved; only files that contained *strictly* the DevWorkspace block are unlinked.
49
+
50
+ - **Doctor & Readiness (`devworkspace doctor`)**:
51
+ `doctor` checks for the presence and freshness of installed instructions for whichever agents are configured in the project. Projects using Cursor, Copilot, or Codex achieve `Core workspace: READY` without requiring `CLAUDE.md`.
52
+
53
+ ## Adding a New Agent Adapter
54
+
55
+ To add support for a new coding agent:
56
+ 1. Create a directory under `src/agents/<agent-name>/` implementing `AgentAdapter`.
57
+ 2. Implement `detectExisting(root)`, `detectAvailable?(root)`, `plan(ctx)`, and `unplan?(root)`.
58
+ 3. Register the adapter in `src/agents/index.ts` under `AGENT_ADAPTERS` and `AGENTS`.
59
+ 4. Add unit and integration tests under `tests/unit/agent-adapters.test.ts`.
60
+
@@ -27,7 +27,7 @@ The CLI contains no business logic. `src/index.ts` exports the services so a fut
27
27
  | `security/` | Sensitive-file rules, secret detection/redaction, safe reading |
28
28
  | `git/` | Porcelain parsing and state (no mutating Git commands anywhere) |
29
29
  | `integrations/` | Integration manager: one `IntegrationAdapter` per tool (observe, plan, verify), consent levels, file protection, state, runner abstraction (Windows shims, npx) |
30
- | `agents/` | `AgentAdapter` interface + the Claude adapter (managed `CLAUDE.md` block, skill) |
30
+ | `agents/` | `AgentAdapter` contract, shared section-merging engine, and concrete adapters for Claude Code, OpenAI Codex (`AGENTS.md`), Cursor (`.cursor/rules` and `.cursorrules`), and GitHub Copilot (`.github/copilot-instructions.md`) |
31
31
  | `prompts/` | Onboarding/refresh prompt builders and the behavioural requirement checker |
32
32
  | `portability/` | Export bundle format and hostile-input validation |
33
33
  | `utils/` | Atomic writes, locking, safe paths, subprocess runner, logger |
@@ -36,7 +36,7 @@ The CLI contains no business logic. `src/index.ts` exports the services so a fut
36
36
 
37
37
  - **Zero runtime dependencies.** Argument parsing uses `node:util`, validation is hand-written, hashing/locking use Node built-ins. Smaller supply-chain surface; dev-only tooling.
38
38
  - **Deterministic core, AI at the edges.** Everything DevWorkspace does itself is reproducible and testable. Reasoning is delegated to the agent through generated prompts, and the prompts are themselves tested against a requirement list (`prompts/requirements.ts`).
39
- - **Adapters at every external boundary.** Core code sees `IntegrationStatus` and `PlannedChange`, never Graphify internals or Claude paths. Adding Cursor/Codex means a new `AgentAdapter`.
39
+ - **Adapters at every external boundary.** Core code sees `IntegrationStatus` and `PlannedChange`, never external agent-specific paths. Coding agent adapters (Claude Code, OpenAI Codex, Cursor, GitHub Copilot) implement the common `AgentAdapter` contract.
40
40
  - **Single routing source.** `tasks/routing.ts` is the table from which the generated skill and `docs/routing.md` are derived.
41
41
  - **State is portable by default.** Anything machine-specific lives in `.devworkspace/local/`. Project identity is a UUID, paths are relative everywhere, and doctor flags absolute paths.
42
42
  - **Fail closed on the future.** Files with a newer `schemaVersion` are never modified; commands refuse with an upgrade hint.
@@ -31,3 +31,22 @@ Report medians and ranges, not single runs. Include the onboarding cost in arm B
31
31
  - Provider token counts are often unavailable inside IDE integrations; use tool-call counts as a proxy and say so.
32
32
  - Savings depend heavily on repository size, task type and knowledge quality. A one-file repo will show none.
33
33
  - Do not extrapolate to percentages without data from your own runs.
34
+
35
+ ## Measuring DevWorkspace itself (speed and context size)
36
+
37
+ The section above is about agent behaviour. This one measures the tool, deterministically and offline.
38
+
39
+ ```
40
+ npm run build
41
+ node scripts/bench.mjs --files 3000 --runs 9 --json result.json
42
+ ```
43
+
44
+ `scripts/bench.mjs` generates an ERP-shaped monorepo (NestJS-style `backend/` with DB-dependent scripts, React `frontend/`, docs, a `.env` that is never read) in a temp directory, commits it, and times the `dist/` build in-process: first and repeated `analyze`, dry-run `analyze`/`refresh`, `refresh`, and context generation (legacy, `--topic`, and `--for` for five request types). It prints medians, min/max and every sample.
45
+
46
+ To compare against a baseline, build the baseline commit in a separate `git worktree`, copy `scripts/bench.mjs` into it, and run the script in both. Alternate the runs (A, B, A, B) to cancel machine drift.
47
+
48
+ **Token counts are estimates**: rendered CLI output bytes / 4. No tokenizer or model is called. Use them to compare two outputs of the same tool, not as billing numbers. The generated fixture's facts are synthetic; real repositories have more and longer facts, so absolute sizes will differ.
49
+
50
+ Reported per context: bytes, estimated tokens, facts included/available, checks included/available, decisions and documents included/available, and how many safety rules and unsafe-command warnings were kept. Cache behaviour (`hit` / `miss` / `invalidated`) is asserted in `tests/integration/progressive-context.test.ts`; correctness of discovery is covered by the existing discovery suites.
51
+
52
+ What the numbers do not show: the scan (`readdir` of every directory) and the Git query still run on every analyze, because the cache may not decide that nothing changed. Both are the remaining bottlenecks.
package/docs/knowledge.md CHANGED
@@ -66,3 +66,20 @@ Never store: source files, `node_modules`/generated output, chat transcripts, te
66
66
  ## Progressive loading
67
67
 
68
68
  The generated instructions tell the agent to load context in levels and stop at the shallowest one that answers the request: (1) instructions → (2) `devworkspace context --topic …` → (3) relevant decisions → (4) Graphify → (5) specific source files → (6) tests/implementation. "Where is customer uniqueness checked?" stops at level 4–5; "Refactor the customer validation architecture" goes to level 6.
69
+
70
+ ### Task-aware context (`context --for`)
71
+
72
+ `devworkspace context --for "<request>"` classifies the request (same classifier as `task new`) and prints only what it needs, each item with the reason it is included:
73
+
74
+ - facts whose key, value or source match the request's keywords, plus type groups (stack and conventions for implementation tasks; known issues for bugs; authorization and tenant rules for security);
75
+ - checks of the package the request names, or of the packages with uncommitted code changes, otherwise all checks; none for questions and documentation;
76
+ - decisions by title and file (never their text) and context documents by name (never embedded);
77
+ - the safety rules, always, and the list of commands DevWorkspace will not run by itself whenever their checks are included.
78
+
79
+ Detector-derived facts whose value equals another selected fact's are folded into it in the output, and very long detector values are clipped with a pointer to `context --topic <key>`. Human- and AI-authored facts are never folded or clipped, and nothing is removed from `facts.json`. Output is deterministic. Plain `context` is unchanged.
80
+
81
+ Routing note (0.3.0): requests naming cross-tenant access, tenant isolation, CSRF/XSS, SQL injection, IDOR, CVEs or privilege escalation are classified SECURITY even when they begin with "fix" (they were BUG), so the exploit-test and review gates apply.
82
+
83
+ ### Discovery cache
84
+
85
+ `analyze` and `refresh` reuse the previous detection result from `.devworkspace/local/cache/discovery.json` (git-ignored, never exported) only when the scan result, the content of every manifest detection reads, the project name, the scan limit and the DevWorkspace version are identical. The directory scan and the Git query always run; the cache is a shortcut for recomputation, never evidence. `--dry-run`, `status`, `doctor` and `context` never write it. Delete the `local/cache` folder at any time.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devworkspace",
3
- "version": "0.2.2",
3
+ "version": "0.3.1",
4
4
  "description": "Project-local AI engineering workspace: deterministic repository discovery, portable project knowledge, tasks, decisions and verification gates for AI coding agents.",
5
5
  "type": "module",
6
6
  "bin": {