devworkspace 0.3.1 → 0.3.3
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.
- package/CHANGELOG.md +31 -1
- package/README.md +3 -1
- package/dist/agents/claude/content.js +27 -4
- package/dist/agents/claude/content.js.map +1 -1
- package/dist/cli/args.d.ts +45 -0
- package/dist/cli/args.js +13 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/help.js +67 -8
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/render.d.ts +10 -0
- package/dist/cli/render.js +197 -1
- package/dist/cli/render.js.map +1 -1
- package/dist/cli/run.js +100 -6
- package/dist/cli/run.js.map +1 -1
- package/dist/config/schemas.d.ts +6 -0
- package/dist/config/schemas.js +6 -0
- package/dist/config/schemas.js.map +1 -1
- package/dist/core/version.d.ts +7 -0
- package/dist/core/version.js +13 -0
- package/dist/core/version.js.map +1 -1
- package/dist/core/workspace.d.ts +1 -0
- package/dist/core/workspace.js +1 -0
- package/dist/core/workspace.js.map +1 -1
- package/dist/discovery/commands.d.ts +1 -0
- package/dist/discovery/commands.js +3 -1
- package/dist/discovery/commands.js.map +1 -1
- package/dist/features/schema.d.ts +3 -0
- package/dist/features/schema.js +79 -0
- package/dist/features/schema.js.map +1 -0
- package/dist/features/types.d.ts +77 -0
- package/dist/features/types.js +2 -0
- package/dist/features/types.js.map +1 -0
- package/dist/features/validate.d.ts +14 -0
- package/dist/features/validate.js +124 -0
- package/dist/features/validate.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/services/doctor.js +34 -0
- package/dist/services/doctor.js.map +1 -1
- package/dist/services/drift.d.ts +21 -0
- package/dist/services/drift.js +89 -0
- package/dist/services/drift.js.map +1 -0
- package/dist/services/features.d.ts +22 -0
- package/dist/services/features.js +105 -0
- package/dist/services/features.js.map +1 -0
- package/dist/services/graph.d.ts +97 -0
- package/dist/services/graph.js +342 -0
- package/dist/services/graph.js.map +1 -0
- package/dist/services/refresh.js +15 -0
- package/dist/services/refresh.js.map +1 -1
- package/dist/services/setup.d.ts +56 -0
- package/dist/services/setup.js +116 -0
- package/dist/services/setup.js.map +1 -0
- package/dist/services/status.d.ts +6 -0
- package/dist/services/status.js +16 -0
- package/dist/services/status.js.map +1 -1
- package/dist/services/taskContext.d.ts +28 -5
- package/dist/services/taskContext.js +221 -4
- package/dist/services/taskContext.js.map +1 -1
- package/dist/services/verify.d.ts +11 -0
- package/dist/services/verify.js +64 -2
- package/dist/services/verify.js.map +1 -1
- package/docs/architecture-report/01-executive-summary.md +168 -0
- package/docs/architecture-report/02-system-architecture.md +356 -0
- package/docs/architecture-report/03-tools-and-technologies.md +112 -0
- package/docs/architecture-report/04-execution-flows.md +257 -0
- package/docs/architecture-report/05-codebase-map.md +150 -0
- package/docs/architecture-report/06-command-reference.md +446 -0
- package/docs/architecture-report/07-data-flow-and-integrations.md +268 -0
- package/docs/architecture-report/08-build-test-deployment.md +185 -0
- package/docs/architecture-report/09-security-and-reliability.md +165 -0
- package/docs/architecture-report/10-feature-status.md +110 -0
- package/docs/architecture-report/11-glossary.md +97 -0
- package/docs/architecture-report/README.md +103 -0
- package/docs/architecture-report/diagrams.md +446 -0
- package/docs/getting-started.md +38 -1
- package/package.json +1 -1
package/dist/services/verify.js
CHANGED
|
@@ -21,6 +21,60 @@ export async function gateContext(ws, task, contract) {
|
|
|
21
21
|
const scope = task.scope ? scopeByPackage(await changedSince(ws.root, task.scope), checks) : null;
|
|
22
22
|
return { contract: checks, scope };
|
|
23
23
|
}
|
|
24
|
+
/** Check if a contract check represents an automated test suite. */
|
|
25
|
+
export function isTestCommand(id, command) {
|
|
26
|
+
if (id === 'unit' || id === 'integration' || id === 'e2e' || id === 'security')
|
|
27
|
+
return true;
|
|
28
|
+
return /\b(test|vitest|jest|pytest|playwright|cypress|mocha|ava|tap)\b/i.test(command);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Detects known zero-test execution signatures from test runners (Vitest, Jest, Pytest, Mocha, Go, Cargo).
|
|
32
|
+
* Prevents commands that exit with code 0 because no tests were found (e.g. --passWithNoTests) from falsely passing.
|
|
33
|
+
*/
|
|
34
|
+
export function detectZeroTests(command, stdout, stderr) {
|
|
35
|
+
const combined = `${stdout}\n${stderr}`;
|
|
36
|
+
const hasPassWithNoTests = /--passWithNoTests\b|--pass-with-no-tests\b/i.test(command);
|
|
37
|
+
// 1. Vitest zero-test patterns
|
|
38
|
+
if (/No test files found/i.test(combined)) {
|
|
39
|
+
return { isZeroTests: true, reason: 'Vitest reported no test files found' };
|
|
40
|
+
}
|
|
41
|
+
if (/Test Files\s+0\s+passed\s*\(0\)/i.test(combined) || /Test Files\s+0\s+failed\s*\(0\)/i.test(combined)) {
|
|
42
|
+
return { isZeroTests: true, reason: 'Vitest ran 0 test files' };
|
|
43
|
+
}
|
|
44
|
+
if (/Tests\s+0\s+passed\s*\(0\)/i.test(combined) && !/Tests\s+[1-9]\d*\s+passed/i.test(combined)) {
|
|
45
|
+
return { isZeroTests: true, reason: 'Vitest ran 0 tests' };
|
|
46
|
+
}
|
|
47
|
+
// 2. Jest zero-test patterns
|
|
48
|
+
if (/Ran all test suites matching.*No tests found/i.test(combined)) {
|
|
49
|
+
return { isZeroTests: true, reason: 'Jest found no test suites matching path' };
|
|
50
|
+
}
|
|
51
|
+
if (/Test Suites:\s*0\s*(?:passed,\s*)?0\s*total/i.test(combined) || /Test Suites:\s*0\s*total/i.test(combined)) {
|
|
52
|
+
return { isZeroTests: true, reason: 'Jest ran 0 test suites' };
|
|
53
|
+
}
|
|
54
|
+
if (/Tests:\s*0\s*(?:passed,\s*)?0\s*total/i.test(combined) || /Tests:\s*0\s*total/i.test(combined)) {
|
|
55
|
+
return { isZeroTests: true, reason: 'Jest ran 0 tests' };
|
|
56
|
+
}
|
|
57
|
+
// 3. Pytest zero-test patterns
|
|
58
|
+
if (/collected 0 items/i.test(combined) || /no tests ran in/i.test(combined)) {
|
|
59
|
+
return { isZeroTests: true, reason: 'Pytest collected and ran 0 items' };
|
|
60
|
+
}
|
|
61
|
+
// 4. Mocha / Node test runner zero-test patterns
|
|
62
|
+
if (/\b0 passing\b/i.test(combined) && !/[1-9]\d*\s+passing\b/i.test(combined)) {
|
|
63
|
+
return { isZeroTests: true, reason: 'Mocha reported 0 passing tests' };
|
|
64
|
+
}
|
|
65
|
+
// 5. Go / Cargo / other zero-test patterns
|
|
66
|
+
if (/\[no test files\]/i.test(combined)) {
|
|
67
|
+
return { isZeroTests: true, reason: 'Go test reported [no test files]' };
|
|
68
|
+
}
|
|
69
|
+
if (/running 0 tests/i.test(combined) && !/running [1-9]\d* tests/i.test(combined)) {
|
|
70
|
+
return { isZeroTests: true, reason: 'Cargo test ran 0 tests' };
|
|
71
|
+
}
|
|
72
|
+
// 6. Explicit passWithNoTests with no tests indicated
|
|
73
|
+
if (hasPassWithNoTests && (/no tests\b/i.test(combined) || /0 tests\b/i.test(combined) || /0 total\b/i.test(combined))) {
|
|
74
|
+
return { isZeroTests: true, reason: 'Command used --passWithNoTests and executed 0 tests' };
|
|
75
|
+
}
|
|
76
|
+
return { isZeroTests: false };
|
|
77
|
+
}
|
|
24
78
|
/** Anything a shell would interpret. Contract commands are run as an argument vector, never through a shell. */
|
|
25
79
|
const SHELL_SYNTAX = /[&|;<>$`(){}"'\\*?!%^\r\n]/;
|
|
26
80
|
export function splitCommand(command) {
|
|
@@ -102,8 +156,16 @@ export async function runTaskCheck(o, id, check, opts = {}) {
|
|
|
102
156
|
runs.push({ ...base, outcome: 'refused', safety: cls.safety, reason: `could not start: ${scrub(r.error)}` });
|
|
103
157
|
continue;
|
|
104
158
|
}
|
|
105
|
-
const
|
|
106
|
-
|
|
159
|
+
const zeroTestResult = isTestCommand(entry.id, entry.command ?? '')
|
|
160
|
+
? detectZeroTests(entry.command ?? '', r.stdout, r.stderr)
|
|
161
|
+
: { isZeroTests: false };
|
|
162
|
+
const isVacuous = r.ok && r.code === 0 && zeroTestResult.isZeroTests;
|
|
163
|
+
const passed = r.ok && r.code === 0 && !isVacuous;
|
|
164
|
+
const tail = !passed
|
|
165
|
+
? isVacuous
|
|
166
|
+
? `vacuous test pass: command exited with code 0 but executed 0 tests (${zeroTestResult.reason})`
|
|
167
|
+
: scrub((r.error ?? '') || (r.timedOut ? 'timed out' : '') || r.stderr.slice(-300) || r.stdout.slice(-300) || `exit ${r.code}`)
|
|
168
|
+
: undefined;
|
|
107
169
|
runs.push({ ...base, outcome: passed ? 'passed' : 'failed', safety: cls.safety, exitCode: r.code, durationMs, ...(tail ? { reason: tail } : {}) });
|
|
108
170
|
evidence.push({ kind: 'executed', at: isoNow(o), package: pkg, command: entry.command ?? undefined, exitCode: r.code, durationMs, scope: fingerprint ?? 'unknown', ...(tail ? { note: tail } : {}) });
|
|
109
171
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/services/verify.ts"],"names":[],"mappings":"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","sourcesContent":["import path from 'node:path';\nimport { SCHEMA_VERSION, validateCommands, type CommandEntry } from '../config/schemas.js';\nimport { requireWorkspace, type Ws } from '../core/workspace.js';\nimport { readVersioned } from '../core/versioned.js';\nimport { DevWorkspaceError } from '../errors/index.js';\nimport { createRunner } from '../integrations/exec.js';\nimport { scrub } from '../integrations/manager.js';\nimport { loadTask, saveTask, type Evidence, type GateContext, type Task, type VerificationItem, type VerifyStatus, assertNoSecrets } from '../tasks/task.js';\nimport { pathExists } from '../utils/fs.js';\nimport { classifyCommand, isDeclared, loadTestEnv, readManifestEvidence, assertSafeTarget, removeDeclaration, saveDeclaration, type CommandSafety, type TestEnvDeclaration } from '../verification/safety.js';\nimport { changedSince, scopeByPackage } from '../verification/scope.js';\nimport { isoNow, mutate, type ServiceOptions } from './common.js';\n\nexport async function loadContract(ws: Ws): Promise<CommandEntry[]> {\n if (!(await pathExists(ws.commands))) return [];\n return (await readVersioned(ws.commands, 'commands', SCHEMA_VERSION, validateCommands)).data.checks;\n}\n\n/** Contract plus what the task has changed per package: everything the completion gate needs to judge it. */\nexport async function gateContext(ws: Ws, task: Task, contract?: CommandEntry[]): Promise<GateContext> {\n const checks = contract ?? (await loadContract(ws));\n const scope = task.scope ? scopeByPackage(await changedSince(ws.root, task.scope), checks) : null;\n return { contract: checks, scope };\n}\n\nexport type RunOutcome = 'passed' | 'failed' | 'refused' | 'not-affected';\n\nexport interface PackageRun {\n package: string;\n command: string | null;\n outcome: RunOutcome;\n exitCode?: number | null;\n durationMs?: number;\n /** Why DevWorkspace did not run it, or the scrubbed tail of a failure. */\n reason?: string;\n safety: CommandSafety['safety'];\n}\n\nexport interface RunCheckResult {\n task: Task;\n check: string;\n runs: PackageRun[];\n}\n\n/** Anything a shell would interpret. Contract commands are run as an argument vector, never through a shell. */\nconst SHELL_SYNTAX = /[&|;<>$`(){}\"'\\\\*?!%^\\r\\n]/;\n\nexport function splitCommand(command: string): string[] | null {\n if (SHELL_SYNTAX.test(command)) return null;\n const parts = command.trim().split(/\\s+/);\n return parts[0] ? parts : null;\n}\n\nconst DEFAULT_TIMEOUT_MS = 10 * 60_000;\n\n/**\n * PATH lookup on Windows finds the extensionless Unix shell script (`npm`) before `npm.cmd`, and that cannot be\n * spawned. Prefer the Windows launcher next to it. Elsewhere the lookup result is used as is.\n */\nexport async function resolveExecutable(found: string | null, name: string, platform: NodeJS.Platform, exists: (p: string) => Promise<boolean>): Promise<string> {\n if (!found) return name;\n if (platform !== 'win32' || /\\.(?:exe|cmd|bat|com)$/i.test(found)) return found;\n for (const ext of ['.cmd', '.exe', '.bat']) if (await exists(found + ext)) return found + ext;\n return found;\n}\n\n/**\n * Run one contract check for the packages the task changed, and record what really happened. DevWorkspace only runs\n * read-only, non-destructive commands; anything that may touch a database runs only after the user declared a test\n * environment for that package and check. Refusals are recorded as \"could not run\", never as a pass.\n */\nexport async function runTaskCheck(o: ServiceOptions, id: string, check: string, opts: { package?: string; timeoutMs?: number } = {}): Promise<RunCheckResult> {\n const ws = await requireWorkspace(o.cwd);\n const task = await loadTask(ws, id);\n const contract = await loadContract(ws);\n const wanted = opts.package ? (opts.package === '.' || opts.package === '' ? '.' : opts.package.replace(/\\/+$/, '')) : undefined;\n const entries = contract.filter((c) => c.id === check && c.available && (wanted === undefined || (c.cwd ?? '.') === wanted));\n if (entries.length === 0) {\n throw new DevWorkspaceError('NOT_FOUND', `The testing contract has no \"${check}\" command${wanted ? ` for package \"${wanted}\"` : ''}.`, {\n hint: 'See the contract with \"devworkspace context\", or add the command to .devworkspace/context/commands.json.',\n });\n }\n const scope = (await gateContext(ws, task, contract)).scope;\n const decls = await loadTestEnv(ws);\n const runner = o.runner ?? createRunner(o.env ?? process.env, o.platform);\n const runs: PackageRun[] = [];\n const evidence: Evidence[] = [];\n\n for (const entry of entries) {\n const pkg = entry.cwd ?? '.';\n const base = { package: pkg, command: entry.command };\n const fingerprint = scope === null ? 'unknown' : scope[pkg];\n if (wanted === undefined && fingerprint === undefined) {\n runs.push({ ...base, outcome: 'not-affected', safety: 'safe', reason: 'the task changed nothing in this package' });\n continue;\n }\n const manifest = await readManifestEvidence(ws.root, entry);\n const cls = classifyCommand(entry, manifest);\n if (cls.safety === 'blocked') {\n runs.push({ ...base, outcome: 'refused', safety: cls.safety, reason: `refused: ${cls.reasons.join('; ')}` });\n continue;\n }\n if (cls.safety === 'needs-test-environment' && !isDeclared(decls, pkg, check)) {\n runs.push({\n ...base,\n outcome: 'refused',\n safety: cls.safety,\n reason: `refused: ${cls.reasons.join('; ')}. Run it yourself against a test environment you control, then declare it with: devworkspace testenv declare --package ${pkg} --check ${check} --target \"<description>\" --yes`,\n });\n continue;\n }\n const argv = splitCommand(entry.command ?? '');\n if (!argv) {\n runs.push({ ...base, outcome: 'refused', safety: cls.safety, reason: 'refused: the command uses shell syntax, and DevWorkspace never runs a shell' });\n continue;\n }\n const exe = await resolveExecutable(await runner.which(argv[0]!), argv[0]!, o.platform ?? process.platform, pathExists);\n const started = Date.now();\n const r = await runner.run({ command: exe, args: argv.slice(1), cwd: pkg === '.' ? ws.root : path.join(ws.root, ...pkg.split('/')), timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS });\n const durationMs = Date.now() - started;\n if (r.code === null && !r.timedOut && r.error) {\n // The command never started (missing executable, bad cwd): that is \"could not run\", not a test failure.\n runs.push({ ...base, outcome: 'refused', safety: cls.safety, reason: `could not start: ${scrub(r.error)}` });\n continue;\n }\n const passed = r.ok && r.code === 0;\n const tail = passed ? undefined : scrub((r.error ?? '') || (r.timedOut ? 'timed out' : '') || r.stderr.slice(-300) || r.stdout.slice(-300) || `exit ${r.code}`);\n runs.push({ ...base, outcome: passed ? 'passed' : 'failed', safety: cls.safety, exitCode: r.code, durationMs, ...(tail ? { reason: tail } : {}) });\n evidence.push({ kind: 'executed', at: isoNow(o), package: pkg, command: entry.command ?? undefined, exitCode: r.code, durationMs, scope: fingerprint ?? 'unknown', ...(tail ? { note: tail } : {}) });\n }\n\n const updated = await mutate(o, async (w) => {\n const t = await loadTask(w, id);\n let item = t.verification.find((v) => v.check === check);\n if (!item) {\n item = { check, required: false, status: 'PENDING' };\n t.verification.push(item);\n }\n for (const e of evidence) item.evidence = [...(item.evidence ?? []).filter((x) => x.package !== e.package), e];\n item.status = summarize(item, runs);\n const refused = runs.filter((r) => r.outcome === 'refused');\n if (item.status === 'COULD_NOT_RUN') item.detail = refused.map((r) => `${r.package}: ${r.reason}`).join(' | ').slice(0, 500);\n else delete item.detail;\n delete item.reason;\n item.updatedAt = isoNow(o);\n t.updatedAt = item.updatedAt;\n assertNoSecrets(t);\n await saveTask(w, t);\n return t;\n });\n return { task: updated, check, runs };\n}\n\nfunction summarize(item: VerificationItem, runs: PackageRun[]): VerifyStatus {\n if (runs.some((r) => r.outcome === 'failed')) return 'FAIL';\n if (runs.some((r) => r.outcome === 'refused')) return 'COULD_NOT_RUN';\n if (runs.some((r) => r.outcome === 'passed')) return 'PASS';\n return item.status;\n}\n\n// ---------- declared test environments ----------\n\nexport async function declareTestEnv(o: ServiceOptions, input: { package: string; checks: string[]; target: string; yes: boolean }): Promise<TestEnvDeclaration> {\n if (!input.yes) {\n throw new DevWorkspaceError('USAGE', 'Declaring a test environment is a statement that running these checks there is safe; confirm with --yes.', {\n hint: 'DevWorkspace cannot verify a database target (it never reads connection settings). This records your assertion, locally, and nothing else.',\n });\n }\n assertSafeTarget(input.target);\n const ws = await requireWorkspace(o.cwd);\n const pkg = input.package === '' ? '.' : input.package.replace(/\\/+$/, '');\n const contract = await loadContract(ws);\n if (!contract.some((c) => c.available && (c.cwd ?? '.') === pkg)) {\n throw new DevWorkspaceError('NOT_FOUND', `The testing contract has no commands for package \"${pkg}\".`, { hint: 'Use a package directory shown by \"devworkspace context\".' });\n }\n const allowed = new Set(['unit', 'integration', 'e2e', 'security']);\n const bad = input.checks.filter((c) => !allowed.has(c));\n if (input.checks.length === 0 || bad.length > 0) {\n throw new DevWorkspaceError('USAGE', `--check must be one or more of: ${[...allowed].join(', ')}.`, { hint: 'Migrations, seeds and resets are never run by DevWorkspace.' });\n }\n const decl: TestEnvDeclaration = { package: pkg, checks: [...new Set(input.checks)].sort(), target: input.target.trim(), declaredAt: isoNow(o) };\n await mutate(o, async (w) => saveDeclaration(w, decl));\n return decl;\n}\n\nexport async function listTestEnv(o: ServiceOptions): Promise<TestEnvDeclaration[]> {\n return loadTestEnv(await requireWorkspace(o.cwd));\n}\n\nexport async function revokeTestEnv(o: ServiceOptions, pkg: string): Promise<boolean> {\n return mutate(o, async (w) => removeDeclaration(w, pkg === '' ? '.' : pkg.replace(/\\/+$/, '')));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/services/verify.ts"],"names":[],"mappings":"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","sourcesContent":["import path from 'node:path';\nimport { SCHEMA_VERSION, validateCommands, type CommandEntry } from '../config/schemas.js';\nimport { requireWorkspace, type Ws } from '../core/workspace.js';\nimport { readVersioned } from '../core/versioned.js';\nimport { DevWorkspaceError } from '../errors/index.js';\nimport { createRunner } from '../integrations/exec.js';\nimport { scrub } from '../integrations/manager.js';\nimport { loadTask, saveTask, type Evidence, type GateContext, type Task, type VerificationItem, type VerifyStatus, assertNoSecrets } from '../tasks/task.js';\nimport { pathExists } from '../utils/fs.js';\nimport { classifyCommand, isDeclared, loadTestEnv, readManifestEvidence, assertSafeTarget, removeDeclaration, saveDeclaration, type CommandSafety, type TestEnvDeclaration } from '../verification/safety.js';\nimport { changedSince, scopeByPackage } from '../verification/scope.js';\nimport { isoNow, mutate, type ServiceOptions } from './common.js';\n\nexport async function loadContract(ws: Ws): Promise<CommandEntry[]> {\n if (!(await pathExists(ws.commands))) return [];\n return (await readVersioned(ws.commands, 'commands', SCHEMA_VERSION, validateCommands)).data.checks;\n}\n\n/** Contract plus what the task has changed per package: everything the completion gate needs to judge it. */\nexport async function gateContext(ws: Ws, task: Task, contract?: CommandEntry[]): Promise<GateContext> {\n const checks = contract ?? (await loadContract(ws));\n const scope = task.scope ? scopeByPackage(await changedSince(ws.root, task.scope), checks) : null;\n return { contract: checks, scope };\n}\n\nexport type RunOutcome = 'passed' | 'failed' | 'refused' | 'not-affected';\n\nexport interface PackageRun {\n package: string;\n command: string | null;\n outcome: RunOutcome;\n exitCode?: number | null;\n durationMs?: number;\n /** Why DevWorkspace did not run it, or the scrubbed tail of a failure. */\n reason?: string;\n safety: CommandSafety['safety'];\n}\n\nexport interface RunCheckResult {\n task: Task;\n check: string;\n runs: PackageRun[];\n}\n\n/** Check if a contract check represents an automated test suite. */\nexport function isTestCommand(id: string, command: string): boolean {\n if (id === 'unit' || id === 'integration' || id === 'e2e' || id === 'security') return true;\n return /\\b(test|vitest|jest|pytest|playwright|cypress|mocha|ava|tap)\\b/i.test(command);\n}\n\nexport interface ZeroTestResult {\n isZeroTests: boolean;\n reason?: string;\n}\n\n/**\n * Detects known zero-test execution signatures from test runners (Vitest, Jest, Pytest, Mocha, Go, Cargo).\n * Prevents commands that exit with code 0 because no tests were found (e.g. --passWithNoTests) from falsely passing.\n */\nexport function detectZeroTests(command: string, stdout: string, stderr: string): ZeroTestResult {\n const combined = `${stdout}\\n${stderr}`;\n const hasPassWithNoTests = /--passWithNoTests\\b|--pass-with-no-tests\\b/i.test(command);\n\n // 1. Vitest zero-test patterns\n if (/No test files found/i.test(combined)) {\n return { isZeroTests: true, reason: 'Vitest reported no test files found' };\n }\n if (/Test Files\\s+0\\s+passed\\s*\\(0\\)/i.test(combined) || /Test Files\\s+0\\s+failed\\s*\\(0\\)/i.test(combined)) {\n return { isZeroTests: true, reason: 'Vitest ran 0 test files' };\n }\n if (/Tests\\s+0\\s+passed\\s*\\(0\\)/i.test(combined) && !/Tests\\s+[1-9]\\d*\\s+passed/i.test(combined)) {\n return { isZeroTests: true, reason: 'Vitest ran 0 tests' };\n }\n\n // 2. Jest zero-test patterns\n if (/Ran all test suites matching.*No tests found/i.test(combined)) {\n return { isZeroTests: true, reason: 'Jest found no test suites matching path' };\n }\n if (/Test Suites:\\s*0\\s*(?:passed,\\s*)?0\\s*total/i.test(combined) || /Test Suites:\\s*0\\s*total/i.test(combined)) {\n return { isZeroTests: true, reason: 'Jest ran 0 test suites' };\n }\n if (/Tests:\\s*0\\s*(?:passed,\\s*)?0\\s*total/i.test(combined) || /Tests:\\s*0\\s*total/i.test(combined)) {\n return { isZeroTests: true, reason: 'Jest ran 0 tests' };\n }\n\n // 3. Pytest zero-test patterns\n if (/collected 0 items/i.test(combined) || /no tests ran in/i.test(combined)) {\n return { isZeroTests: true, reason: 'Pytest collected and ran 0 items' };\n }\n\n // 4. Mocha / Node test runner zero-test patterns\n if (/\\b0 passing\\b/i.test(combined) && !/[1-9]\\d*\\s+passing\\b/i.test(combined)) {\n return { isZeroTests: true, reason: 'Mocha reported 0 passing tests' };\n }\n\n // 5. Go / Cargo / other zero-test patterns\n if (/\\[no test files\\]/i.test(combined)) {\n return { isZeroTests: true, reason: 'Go test reported [no test files]' };\n }\n if (/running 0 tests/i.test(combined) && !/running [1-9]\\d* tests/i.test(combined)) {\n return { isZeroTests: true, reason: 'Cargo test ran 0 tests' };\n }\n\n // 6. Explicit passWithNoTests with no tests indicated\n if (hasPassWithNoTests && (/no tests\\b/i.test(combined) || /0 tests\\b/i.test(combined) || /0 total\\b/i.test(combined))) {\n return { isZeroTests: true, reason: 'Command used --passWithNoTests and executed 0 tests' };\n }\n\n return { isZeroTests: false };\n}\n\n/** Anything a shell would interpret. Contract commands are run as an argument vector, never through a shell. */\nconst SHELL_SYNTAX = /[&|;<>$`(){}\"'\\\\*?!%^\\r\\n]/;\n\nexport function splitCommand(command: string): string[] | null {\n if (SHELL_SYNTAX.test(command)) return null;\n const parts = command.trim().split(/\\s+/);\n return parts[0] ? parts : null;\n}\n\nconst DEFAULT_TIMEOUT_MS = 10 * 60_000;\n\n/**\n * PATH lookup on Windows finds the extensionless Unix shell script (`npm`) before `npm.cmd`, and that cannot be\n * spawned. Prefer the Windows launcher next to it. Elsewhere the lookup result is used as is.\n */\nexport async function resolveExecutable(found: string | null, name: string, platform: NodeJS.Platform, exists: (p: string) => Promise<boolean>): Promise<string> {\n if (!found) return name;\n if (platform !== 'win32' || /\\.(?:exe|cmd|bat|com)$/i.test(found)) return found;\n for (const ext of ['.cmd', '.exe', '.bat']) if (await exists(found + ext)) return found + ext;\n return found;\n}\n\n/**\n * Run one contract check for the packages the task changed, and record what really happened. DevWorkspace only runs\n * read-only, non-destructive commands; anything that may touch a database runs only after the user declared a test\n * environment for that package and check. Refusals are recorded as \"could not run\", never as a pass.\n */\nexport async function runTaskCheck(o: ServiceOptions, id: string, check: string, opts: { package?: string; timeoutMs?: number } = {}): Promise<RunCheckResult> {\n const ws = await requireWorkspace(o.cwd);\n const task = await loadTask(ws, id);\n const contract = await loadContract(ws);\n const wanted = opts.package ? (opts.package === '.' || opts.package === '' ? '.' : opts.package.replace(/\\/+$/, '')) : undefined;\n const entries = contract.filter((c) => c.id === check && c.available && (wanted === undefined || (c.cwd ?? '.') === wanted));\n if (entries.length === 0) {\n throw new DevWorkspaceError('NOT_FOUND', `The testing contract has no \"${check}\" command${wanted ? ` for package \"${wanted}\"` : ''}.`, {\n hint: 'See the contract with \"devworkspace context\", or add the command to .devworkspace/context/commands.json.',\n });\n }\n const scope = (await gateContext(ws, task, contract)).scope;\n const decls = await loadTestEnv(ws);\n const runner = o.runner ?? createRunner(o.env ?? process.env, o.platform);\n const runs: PackageRun[] = [];\n const evidence: Evidence[] = [];\n\n for (const entry of entries) {\n const pkg = entry.cwd ?? '.';\n const base = { package: pkg, command: entry.command };\n const fingerprint = scope === null ? 'unknown' : scope[pkg];\n if (wanted === undefined && fingerprint === undefined) {\n runs.push({ ...base, outcome: 'not-affected', safety: 'safe', reason: 'the task changed nothing in this package' });\n continue;\n }\n const manifest = await readManifestEvidence(ws.root, entry);\n const cls = classifyCommand(entry, manifest);\n if (cls.safety === 'blocked') {\n runs.push({ ...base, outcome: 'refused', safety: cls.safety, reason: `refused: ${cls.reasons.join('; ')}` });\n continue;\n }\n if (cls.safety === 'needs-test-environment' && !isDeclared(decls, pkg, check)) {\n runs.push({\n ...base,\n outcome: 'refused',\n safety: cls.safety,\n reason: `refused: ${cls.reasons.join('; ')}. Run it yourself against a test environment you control, then declare it with: devworkspace testenv declare --package ${pkg} --check ${check} --target \"<description>\" --yes`,\n });\n continue;\n }\n const argv = splitCommand(entry.command ?? '');\n if (!argv) {\n runs.push({ ...base, outcome: 'refused', safety: cls.safety, reason: 'refused: the command uses shell syntax, and DevWorkspace never runs a shell' });\n continue;\n }\n const exe = await resolveExecutable(await runner.which(argv[0]!), argv[0]!, o.platform ?? process.platform, pathExists);\n const started = Date.now();\n const r = await runner.run({ command: exe, args: argv.slice(1), cwd: pkg === '.' ? ws.root : path.join(ws.root, ...pkg.split('/')), timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS });\n const durationMs = Date.now() - started;\n if (r.code === null && !r.timedOut && r.error) {\n // The command never started (missing executable, bad cwd): that is \"could not run\", not a test failure.\n runs.push({ ...base, outcome: 'refused', safety: cls.safety, reason: `could not start: ${scrub(r.error)}` });\n continue;\n }\n const zeroTestResult = isTestCommand(entry.id, entry.command ?? '')\n ? detectZeroTests(entry.command ?? '', r.stdout, r.stderr)\n : { isZeroTests: false };\n const isVacuous = r.ok && r.code === 0 && zeroTestResult.isZeroTests;\n const passed = r.ok && r.code === 0 && !isVacuous;\n const tail = !passed\n ? isVacuous\n ? `vacuous test pass: command exited with code 0 but executed 0 tests (${zeroTestResult.reason})`\n : scrub((r.error ?? '') || (r.timedOut ? 'timed out' : '') || r.stderr.slice(-300) || r.stdout.slice(-300) || `exit ${r.code}`)\n : undefined;\n runs.push({ ...base, outcome: passed ? 'passed' : 'failed', safety: cls.safety, exitCode: r.code, durationMs, ...(tail ? { reason: tail } : {}) });\n evidence.push({ kind: 'executed', at: isoNow(o), package: pkg, command: entry.command ?? undefined, exitCode: r.code, durationMs, scope: fingerprint ?? 'unknown', ...(tail ? { note: tail } : {}) });\n }\n\n const updated = await mutate(o, async (w) => {\n const t = await loadTask(w, id);\n let item = t.verification.find((v) => v.check === check);\n if (!item) {\n item = { check, required: false, status: 'PENDING' };\n t.verification.push(item);\n }\n for (const e of evidence) item.evidence = [...(item.evidence ?? []).filter((x) => x.package !== e.package), e];\n item.status = summarize(item, runs);\n const refused = runs.filter((r) => r.outcome === 'refused');\n if (item.status === 'COULD_NOT_RUN') item.detail = refused.map((r) => `${r.package}: ${r.reason}`).join(' | ').slice(0, 500);\n else delete item.detail;\n delete item.reason;\n item.updatedAt = isoNow(o);\n t.updatedAt = item.updatedAt;\n assertNoSecrets(t);\n await saveTask(w, t);\n return t;\n });\n return { task: updated, check, runs };\n}\n\nfunction summarize(item: VerificationItem, runs: PackageRun[]): VerifyStatus {\n if (runs.some((r) => r.outcome === 'failed')) return 'FAIL';\n if (runs.some((r) => r.outcome === 'refused')) return 'COULD_NOT_RUN';\n if (runs.some((r) => r.outcome === 'passed')) return 'PASS';\n return item.status;\n}\n\n// ---------- declared test environments ----------\n\nexport async function declareTestEnv(o: ServiceOptions, input: { package: string; checks: string[]; target: string; yes: boolean }): Promise<TestEnvDeclaration> {\n if (!input.yes) {\n throw new DevWorkspaceError('USAGE', 'Declaring a test environment is a statement that running these checks there is safe; confirm with --yes.', {\n hint: 'DevWorkspace cannot verify a database target (it never reads connection settings). This records your assertion, locally, and nothing else.',\n });\n }\n assertSafeTarget(input.target);\n const ws = await requireWorkspace(o.cwd);\n const pkg = input.package === '' ? '.' : input.package.replace(/\\/+$/, '');\n const contract = await loadContract(ws);\n if (!contract.some((c) => c.available && (c.cwd ?? '.') === pkg)) {\n throw new DevWorkspaceError('NOT_FOUND', `The testing contract has no commands for package \"${pkg}\".`, { hint: 'Use a package directory shown by \"devworkspace context\".' });\n }\n const allowed = new Set(['unit', 'integration', 'e2e', 'security']);\n const bad = input.checks.filter((c) => !allowed.has(c));\n if (input.checks.length === 0 || bad.length > 0) {\n throw new DevWorkspaceError('USAGE', `--check must be one or more of: ${[...allowed].join(', ')}.`, { hint: 'Migrations, seeds and resets are never run by DevWorkspace.' });\n }\n const decl: TestEnvDeclaration = { package: pkg, checks: [...new Set(input.checks)].sort(), target: input.target.trim(), declaredAt: isoNow(o) };\n await mutate(o, async (w) => saveDeclaration(w, decl));\n return decl;\n}\n\nexport async function listTestEnv(o: ServiceOptions): Promise<TestEnvDeclaration[]> {\n return loadTestEnv(await requireWorkspace(o.cwd));\n}\n\nexport async function revokeTestEnv(o: ServiceOptions, pkg: string): Promise<boolean> {\n return mutate(o, async (w) => removeDeclaration(w, pkg === '' ? '.' : pkg.replace(/\\/+$/, '')));\n}\n"]}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Chapter 1: Executive Summary
|
|
2
|
+
|
|
3
|
+
> **A beginner-friendly overview of what DevWorkspace is, why it was created, and how it transforms AI-assisted software engineering.**
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. The Core Problem DevWorkspace Solves
|
|
8
|
+
|
|
9
|
+
Modern AI coding agents (such as Claude Code, Cursor, OpenAI Codex, GitHub Copilot, and Google Antigravity) are remarkably capable at writing code. However, when dropped into a new or complex repository, they suffer from predictable, recurring failure modes:
|
|
10
|
+
|
|
11
|
+
1. **Context Window Exhaustion**: Agents attempt to read hundreds of files or entire directories, filling up their context window with irrelevant code and forgetting instructions.
|
|
12
|
+
2. **Hallucinated Project Commands**: In multi-package or monorepo setups (such as a repository with `backend/` and `frontend/` but no root `package.json`), agents attempt to run `npm test` or `npm run build` at the root, failing immediately.
|
|
13
|
+
3. **Accidental Destruction of User Work**: Agents often run `git reset --hard` or overwrite uncommitted changes when debugging or attempting to reproduce an issue.
|
|
14
|
+
4. **Credential Leaks**: Agents accidentally read `.env`, `.env.local`, or private key files, exposing secrets in prompts, logs, or commit messages.
|
|
15
|
+
5. **Hallucinated Completion**: An agent writes code, encounters a failing build or test, ignores the failure, and prematurely declares: *"All tasks are complete and verified!"*
|
|
16
|
+
|
|
17
|
+
**DevWorkspace was built to eliminate these exact failure modes.**
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 2. What is DevWorkspace?
|
|
22
|
+
|
|
23
|
+
**DevWorkspace** is a project-local CLI tool that acts as a deterministic, persistent operating environment for AI coding agents.
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
+---------------------------------------------------------------------------------------+
|
|
27
|
+
| DEVWORKSPACE |
|
|
28
|
+
| |
|
|
29
|
+
| 1. Discovers the repo deterministically (No AI involved in scanning) |
|
|
30
|
+
| 2. Writes portable facts and testing contracts into .devworkspace/ |
|
|
31
|
+
| 3. Installs instruction files (AGENTS.md, CLAUDE.md, etc.) non-destructively |
|
|
32
|
+
| 4. Enforces strict verification gates before any task can be marked COMPLETED |
|
|
33
|
+
| 5. Requires zero external npm runtime dependencies (100% Node.js built-ins) |
|
|
34
|
+
+---------------------------------------------------------------------------------------+
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
When you initialize DevWorkspace in a repository, it creates a lightweight `.devworkspace/` directory. Inside, it stores facts about the project's tech stack, package structure, entry points, architectural decisions (ADRs), and tasks. It then injects non-destructive instructions into whatever AI agent tools the developer uses.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 3. The Real-World Analogy: An Airport Control Tower
|
|
42
|
+
|
|
43
|
+
Think of an AI coding agent as a **visiting pilot flying an aircraft**.
|
|
44
|
+
- The pilot is skilled and capable of flying the plane.
|
|
45
|
+
- However, the pilot does not know the specific runway layout, local weather conditions, mountain obstacles, or taxiway rules of this particular airport.
|
|
46
|
+
- If left alone without ground guidance, the pilot might try to land on an active construction zone or taxi across an occupied runway.
|
|
47
|
+
|
|
48
|
+
**DevWorkspace is the Airport Control Tower and Navigational Chart:**
|
|
49
|
+
- **Navigational Chart (`.devworkspace/context/`)**: Tells the pilot exactly where the runways are (`backend`, `frontend`), which radio frequencies to use (exact `npm run ...` commands and their working directories), and where the hazardous zones are (`.env` files, production migrations).
|
|
50
|
+
- **Control Tower Logs (`.devworkspace/tasks/`)**: Keeps a continuous log of every flight maneuver. The pilot cannot claim they have landed safely (`COMPLETED`) until the tower confirms the wheels have touched down and the runway sensors register `PASS`.
|
|
51
|
+
- **Flight Operating Rules (`AGENTS.md` / `CLAUDE.md`)**: A clear checklist in the cockpit explaining the mandatory safety protocol: never dump fuel over cities (never wipe uncommitted user work), check wind conditions before descent (run `git status` first).
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 4. The Five Architectural Pillars
|
|
56
|
+
|
|
57
|
+
DevWorkspace is built around five core principles enforced across its codebase:
|
|
58
|
+
|
|
59
|
+
```mermaid
|
|
60
|
+
flowchart LR
|
|
61
|
+
P1["1. Deterministic Discovery\n(No LLM hallucination in repo scanning)"]
|
|
62
|
+
P2["2. Progressive Context Loading\n(Level 1 to Level 6 shallow-first)"]
|
|
63
|
+
P3["3. Multi-Agent Adapters\n(Claude, Codex, Cursor, Copilot)"]
|
|
64
|
+
P4["4. Completion Verification Gates\n(Evidence over claims, regression tracking)"]
|
|
65
|
+
P5["5. Zero-Secret Invariants\n(Never read .env, symlink & path defense)"]
|
|
66
|
+
|
|
67
|
+
P1 --- P2 --- P3 --- P4 --- P5
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Pillar 1: Deterministic Discovery
|
|
71
|
+
DevWorkspace never uses an AI model to discover a repository. Its analysis engine (`src/discovery/`) uses fast, deterministic filesystem scanning, manifest parsing (`package.json`, `Cargo.toml`, `pyproject.toml`), and extension counting. It is 100% reproducible, runs in under a second even on repositories with thousands of files, and costs zero API tokens.
|
|
72
|
+
|
|
73
|
+
### Pillar 2: Progressive Context Loading
|
|
74
|
+
To keep agent token consumption minimal, DevWorkspace structures project knowledge into shallow tiers. An agent reads high-level operating rules first (Level 1), queries specific facts if needed (Level 2), inspects relevant architecture decisions (Level 3), and only reads source code and tests (Level 5–6) for the files directly touched by the task.
|
|
75
|
+
|
|
76
|
+
### Pillar 3: Multi-Agent Compatibility
|
|
77
|
+
DevWorkspace is not locked into a single AI tool. It features a pluggable adapter system (`src/agents/`) that non-destructively generates and updates native instruction files:
|
|
78
|
+
- **OpenAI Codex & Antigravity**: `AGENTS.md`
|
|
79
|
+
- **Claude Code**: `CLAUDE.md` and `.claude/skills/devworkspace/SKILL.md`
|
|
80
|
+
- **Cursor IDE**: `.cursor/rules/devworkspace.mdc`
|
|
81
|
+
- **GitHub Copilot**: `.github/copilot-instructions.md`
|
|
82
|
+
|
|
83
|
+
All adapters preserve existing human-written instructions by enclosing DevWorkspace rules within dedicated comment blocks: `<!-- devworkspace:start --> ... <!-- devworkspace:end -->`.
|
|
84
|
+
|
|
85
|
+
### Pillar 4: Evidence-Based Completion Gates
|
|
86
|
+
In DevWorkspace, a task status is a state machine: `IDEA` -> `SPECIFIED` -> `PLANNED` -> `IN_PROGRESS` -> `TESTING` -> `REVIEW` -> `COMPLETED`. An agent **cannot** transition a task to `COMPLETED` by simply asserting that it is finished. The completion gate (`evaluateGate` in `src/tasks/task.ts`) inspects real execution evidence:
|
|
87
|
+
- Did required typechecks, linting, tests, and builds pass?
|
|
88
|
+
- Did passing test counts drop, or did failures increase compared to baseline?
|
|
89
|
+
- Did the agent respect read-only constraints?
|
|
90
|
+
- Has the agent confirmed the three mandatory human attestations (user work intact, knowledge reviewed, final state inspected)?
|
|
91
|
+
|
|
92
|
+
If any check fails, the transition is rejected with a `GATE` error.
|
|
93
|
+
|
|
94
|
+
### Pillar 5: Zero-Secret & Path Security Invariants
|
|
95
|
+
DevWorkspace treats the repository as **untrusted data**:
|
|
96
|
+
- **Never reads credential files**: `.env`, `.env.local`, `.pem`, `.key`, and service account tokens are detected by name only; their bytes are never read into memory.
|
|
97
|
+
- **Symlink traversal defense**: Analysis code (`safeReadText`) uses realpath canonicalization and checks every path component to prevent directory traversal and symlink escape attacks.
|
|
98
|
+
- **Database safety**: Commands containing destructive terms (`drop`, `truncate`, `flushall`, `migrate`, `seed`) or requiring live databases are blocked from running automatically unless a developer explicitly declares a local test environment (`devworkspace testenv declare`).
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 5. A Beginner-Friendly Walkthrough of One Representative Run
|
|
103
|
+
|
|
104
|
+
Here is what happens in practice when a developer or agent uses DevWorkspace in a project:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# Step 1: Initialize DevWorkspace in the repository
|
|
108
|
+
$ npx devworkspace init
|
|
109
|
+
✓ DevWorkspace initialized.
|
|
110
|
+
Created: .devworkspace/ (project.json, context/, tasks/, decisions/, analysis/)
|
|
111
|
+
|
|
112
|
+
# Step 2: Run deterministic repository discovery
|
|
113
|
+
$ npx devworkspace analyze
|
|
114
|
+
✓ Analysis complete (deterministic discovery; no AI involved).
|
|
115
|
+
Files scanned: 893
|
|
116
|
+
Ecosystems: node; package manager: npm
|
|
117
|
+
Monorepo: multi-package (backend, frontend)
|
|
118
|
+
Verification commands found: typecheck, unit, integration, lint, build
|
|
119
|
+
Wrote: .devworkspace/context/commands.json
|
|
120
|
+
Wrote: .devworkspace/analysis/onboarding-prompt.md
|
|
121
|
+
|
|
122
|
+
# Step 3: Install instructions for your coding agent
|
|
123
|
+
$ npx devworkspace agent install --agent all
|
|
124
|
+
✓ codex instructions processed: CREATE AGENTS.md
|
|
125
|
+
✓ cursor instructions processed: CREATE .cursor/rules/devworkspace.mdc
|
|
126
|
+
✓ copilot instructions processed: CREATE .github/copilot-instructions.md
|
|
127
|
+
|
|
128
|
+
# Step 4: The agent starts working on a new feature
|
|
129
|
+
$ npx devworkspace task new "Add invoice PDF generation endpoint"
|
|
130
|
+
✓ Created T-20261102-4a1b
|
|
131
|
+
type: FEATURE (classified)
|
|
132
|
+
required verification: unit, typecheck, lint, build, review
|
|
133
|
+
workflow: Check git status. Read relevant facts and decisions.
|
|
134
|
+
|
|
135
|
+
# Step 5: After coding, the agent runs contract checks
|
|
136
|
+
$ npx devworkspace task run T-20261102-4a1b unit
|
|
137
|
+
ok [backend] npm run test:unit (exit code 0, 1.4s)
|
|
138
|
+
ok [frontend] npm test (exit code 0, 2.1s)
|
|
139
|
+
|
|
140
|
+
# Step 6: The agent attests that user work was untouched and finishes
|
|
141
|
+
$ npx devworkspace task attest T-20261102-4a1b --user-work-intact --knowledge-reviewed --final-state-inspected
|
|
142
|
+
✓ Attestations recorded.
|
|
143
|
+
|
|
144
|
+
$ npx devworkspace task status T-20261102-4a1b COMPLETED --note "Implemented with PDFKit; 14 tests passing"
|
|
145
|
+
✓ T-20261102-4a1b is now COMPLETED
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 6. Zero Runtime Dependencies
|
|
151
|
+
|
|
152
|
+
One of the most remarkable technical decisions in DevWorkspace is that `package.json` contains:
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
"dependencies": {}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
DevWorkspace has **zero third-party production dependencies**. Everything—JSON validation, child process execution, argument parsing, file hashing, symlink canonicalization, lockfiles, and terminal formatting—is implemented entirely using standard **Node.js 20+ built-in modules** (`node:fs/promises`, `node:path`, `node:child_process`, `node:crypto`, `node:util`, `node:os`).
|
|
159
|
+
|
|
160
|
+
This guarantees:
|
|
161
|
+
- **Instant execution**: No `node_modules` installation delay when running via `npx devworkspace`.
|
|
162
|
+
- **Zero supply-chain vulnerability risk**: No deeply nested third-party packages that could be hijacked.
|
|
163
|
+
- **Maximum portability**: Runs identically across Linux, macOS, and Windows.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
*Continue to [Chapter 2: System Architecture](./02-system-architecture.md) for the detailed layer breakdown and component interaction model.*
|
|
168
|
+
|