@paths.design/caws-cli 7.0.1 → 7.0.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.
Files changed (121) hide show
  1. package/dist/budget-derivation.js +5 -4
  2. package/dist/commands/diagnose.js +26 -20
  3. package/dist/commands/init.js +72 -5
  4. package/dist/commands/specs.js +40 -1
  5. package/dist/commands/status.js +2 -2
  6. package/dist/commands/templates.js +10 -0
  7. package/dist/commands/tool.js +2 -3
  8. package/dist/commands/validate.js +12 -0
  9. package/dist/config/index.js +17 -8
  10. package/dist/generators/working-spec.js +42 -9
  11. package/dist/index.js +3 -1
  12. package/dist/scaffold/cursor-hooks.js +10 -2
  13. package/dist/scaffold/git-hooks.js +189 -32
  14. package/dist/scaffold/index.js +105 -17
  15. package/dist/templates/.caws/tools/README.md +20 -0
  16. package/dist/templates/.cursor/README.md +311 -0
  17. package/dist/templates/.cursor/hooks/audit.sh +55 -0
  18. package/dist/templates/.cursor/hooks/block-dangerous.sh +83 -0
  19. package/dist/templates/.cursor/hooks/caws-quality-check.sh +52 -0
  20. package/dist/templates/.cursor/hooks/caws-scope-guard.sh +130 -0
  21. package/dist/templates/.cursor/hooks/caws-tool-validation.sh +121 -0
  22. package/dist/templates/.cursor/hooks/format.sh +38 -0
  23. package/dist/templates/.cursor/hooks/naming-check.sh +64 -0
  24. package/dist/templates/.cursor/hooks/scan-secrets.sh +46 -0
  25. package/dist/templates/.cursor/hooks/scope-guard.sh +52 -0
  26. package/dist/templates/.cursor/hooks/validate-spec.sh +83 -0
  27. package/dist/templates/.cursor/hooks.json +59 -0
  28. package/dist/templates/.cursor/rules/00-claims-verification.mdc +144 -0
  29. package/dist/templates/.cursor/rules/01-working-style.mdc +50 -0
  30. package/dist/templates/.cursor/rules/02-quality-gates.mdc +370 -0
  31. package/dist/templates/.cursor/rules/03-naming-and-refactor.mdc +33 -0
  32. package/dist/templates/.cursor/rules/04-logging-language-style.mdc +23 -0
  33. package/dist/templates/.cursor/rules/05-safe-defaults-guards.mdc +23 -0
  34. package/dist/templates/.cursor/rules/06-typescript-conventions.mdc +36 -0
  35. package/dist/templates/.cursor/rules/07-process-ops.mdc +20 -0
  36. package/dist/templates/.cursor/rules/08-solid-and-architecture.mdc +16 -0
  37. package/dist/templates/.cursor/rules/09-docstrings.mdc +89 -0
  38. package/dist/templates/.cursor/rules/10-documentation-quality-standards.mdc +390 -0
  39. package/dist/templates/.cursor/rules/11-scope-management-waivers.mdc +385 -0
  40. package/dist/templates/.cursor/rules/12-implementation-completeness.mdc +516 -0
  41. package/dist/templates/.cursor/rules/13-language-agnostic-standards.mdc +588 -0
  42. package/dist/templates/.cursor/rules/README.md +148 -0
  43. package/dist/templates/.github/copilot/instructions.md +311 -0
  44. package/dist/templates/.idea/runConfigurations/CAWS_Evaluate.xml +5 -0
  45. package/dist/templates/.idea/runConfigurations/CAWS_Validate.xml +5 -0
  46. package/dist/templates/.vscode/launch.json +56 -0
  47. package/dist/templates/.vscode/settings.json +93 -0
  48. package/dist/templates/.windsurf/workflows/caws-guided-development.md +92 -0
  49. package/dist/templates/COMMIT_CONVENTIONS.md +86 -0
  50. package/dist/templates/OIDC_SETUP.md +300 -0
  51. package/dist/templates/agents.md +1047 -0
  52. package/dist/templates/codemod/README.md +1 -0
  53. package/dist/templates/codemod/test.js +93 -0
  54. package/dist/templates/docs/README.md +150 -0
  55. package/dist/templates/scripts/quality-gates/check-god-objects.js +146 -0
  56. package/dist/templates/scripts/quality-gates/run-quality-gates.js +50 -0
  57. package/dist/templates/scripts/v3/analysis/todo_analyzer.py +1997 -0
  58. package/dist/tool-loader.js +6 -1
  59. package/dist/tool-validator.js +8 -2
  60. package/dist/utils/detection.js +34 -6
  61. package/dist/utils/git-lock.js +118 -0
  62. package/dist/utils/gitignore-updater.js +148 -0
  63. package/dist/utils/quality-gates.js +47 -7
  64. package/dist/utils/spec-resolver.js +23 -3
  65. package/dist/utils/yaml-validation.js +155 -0
  66. package/dist/validation/spec-validation.js +105 -2
  67. package/package.json +2 -2
  68. package/templates/.caws/schemas/waivers.schema.json +30 -0
  69. package/templates/.caws/schemas/working-spec.schema.json +133 -0
  70. package/templates/.caws/templates/working-spec.template.yml +74 -0
  71. package/templates/.caws/tools/README.md +20 -0
  72. package/templates/.caws/tools/scope-guard.js +208 -0
  73. package/templates/.caws/tools-allow.json +331 -0
  74. package/templates/.caws/waivers.yml +19 -0
  75. package/templates/.cursor/hooks/scope-guard.sh +2 -2
  76. package/templates/.cursor/hooks/validate-spec.sh +42 -7
  77. package/templates/apps/tools/caws/COMPLETION_REPORT.md +0 -331
  78. package/templates/apps/tools/caws/MIGRATION_SUMMARY.md +0 -360
  79. package/templates/apps/tools/caws/README.md +0 -463
  80. package/templates/apps/tools/caws/TEST_STATUS.md +0 -365
  81. package/templates/apps/tools/caws/attest.js +0 -357
  82. package/templates/apps/tools/caws/ci-optimizer.js +0 -642
  83. package/templates/apps/tools/caws/config.ts +0 -245
  84. package/templates/apps/tools/caws/cross-functional.js +0 -876
  85. package/templates/apps/tools/caws/dashboard.js +0 -1112
  86. package/templates/apps/tools/caws/flake-detector.ts +0 -362
  87. package/templates/apps/tools/caws/gates.js +0 -198
  88. package/templates/apps/tools/caws/gates.ts +0 -271
  89. package/templates/apps/tools/caws/language-adapters.ts +0 -381
  90. package/templates/apps/tools/caws/language-support.d.ts +0 -367
  91. package/templates/apps/tools/caws/language-support.d.ts.map +0 -1
  92. package/templates/apps/tools/caws/language-support.js +0 -585
  93. package/templates/apps/tools/caws/legacy-assessment.ts +0 -408
  94. package/templates/apps/tools/caws/legacy-assessor.js +0 -764
  95. package/templates/apps/tools/caws/mutant-analyzer.js +0 -734
  96. package/templates/apps/tools/caws/perf-budgets.ts +0 -349
  97. package/templates/apps/tools/caws/prompt-lint.js.backup +0 -274
  98. package/templates/apps/tools/caws/property-testing.js +0 -707
  99. package/templates/apps/tools/caws/provenance.d.ts +0 -14
  100. package/templates/apps/tools/caws/provenance.d.ts.map +0 -1
  101. package/templates/apps/tools/caws/provenance.js +0 -132
  102. package/templates/apps/tools/caws/provenance.js.backup +0 -73
  103. package/templates/apps/tools/caws/provenance.ts +0 -211
  104. package/templates/apps/tools/caws/security-provenance.ts +0 -483
  105. package/templates/apps/tools/caws/shared/base-tool.ts +0 -281
  106. package/templates/apps/tools/caws/shared/config-manager.ts +0 -366
  107. package/templates/apps/tools/caws/shared/gate-checker.ts +0 -849
  108. package/templates/apps/tools/caws/shared/types.ts +0 -444
  109. package/templates/apps/tools/caws/shared/validator.ts +0 -305
  110. package/templates/apps/tools/caws/shared/waivers-manager.ts +0 -174
  111. package/templates/apps/tools/caws/spec-test-mapper.ts +0 -391
  112. package/templates/apps/tools/caws/test-quality.js +0 -578
  113. package/templates/apps/tools/caws/validate.js +0 -76
  114. package/templates/apps/tools/caws/validate.ts +0 -228
  115. package/templates/apps/tools/caws/waivers.js +0 -344
  116. /package/{templates/apps/tools/caws → dist/templates/.caws}/schemas/waivers.schema.json +0 -0
  117. /package/{templates/apps/tools/caws → dist/templates/.caws}/schemas/working-spec.schema.json +0 -0
  118. /package/{templates/apps/tools/caws → dist/templates/.caws}/templates/working-spec.template.yml +0 -0
  119. /package/{templates/apps/tools/caws → dist/templates/.caws/tools}/scope-guard.js +0 -0
  120. /package/{templates/apps/tools/caws → dist/templates/.caws}/tools-allow.json +0 -0
  121. /package/{templates/apps/tools/caws → dist/templates/.caws}/waivers.yml +0 -0
@@ -238,6 +238,42 @@ function validateWorkingSpecWithSuggestions(spec, options = {}) {
238
238
  }
239
239
  }
240
240
 
241
+ // Validate scope.out doesn't contain glob patterns
242
+ if (spec.scope && spec.scope.out && Array.isArray(spec.scope.out)) {
243
+ const globPatterns = spec.scope.out.filter(
244
+ (pattern) => pattern.includes('*') || pattern.includes('?')
245
+ );
246
+ if (globPatterns.length > 0) {
247
+ errors.push({
248
+ instancePath: '/scope/out',
249
+ message: `Unsupported glob patterns in scope.out: ${globPatterns.join(', ')}`,
250
+ suggestion:
251
+ 'Use directory paths only (e.g., __pycache__/ instead of *.pyc or **/*.pyc). Python cache files are already covered by __pycache__/',
252
+ canAutoFix: true,
253
+ });
254
+
255
+ // Auto-fix: remove glob patterns and keep only directory paths
256
+ if (autoFix) {
257
+ const fixedOut = spec.scope.out
258
+ .filter((pattern) => !pattern.includes('*') && !pattern.includes('?'))
259
+ .map((pattern) => {
260
+ // Ensure directory paths end with /
261
+ if (!pattern.includes('.') && !pattern.endsWith('/')) {
262
+ return pattern + '/';
263
+ }
264
+ return pattern;
265
+ });
266
+
267
+ fixes.push({
268
+ field: 'scope.out',
269
+ value: fixedOut,
270
+ description: `Removed glob patterns from scope.out: ${globPatterns.join(', ')}`,
271
+ reason: 'Glob patterns are not supported in scope.out',
272
+ });
273
+ }
274
+ }
275
+ }
276
+
241
277
  // Auto-fix missing scope.out
242
278
  if (spec.scope && !spec.scope.out) {
243
279
  fixes.push({
@@ -325,11 +361,36 @@ function validateWorkingSpecWithSuggestions(spec, options = {}) {
325
361
  // Tier-specific validations
326
362
  if (spec.risk_tier === 1 || spec.risk_tier === 2) {
327
363
  if (!spec.contracts || spec.contracts.length === 0) {
364
+ const isChoreMode = spec.mode === 'chore';
365
+ const suggestion = isChoreMode
366
+ ? 'For infrastructure/setup work, add a minimal project_setup contract or create a waiver'
367
+ : 'Add API contracts (OpenAPI, GraphQL, etc.) or change mode to "chore" for maintenance work';
368
+
328
369
  errors.push({
329
370
  instancePath: '/contracts',
330
- message: 'Contracts required for Tier 1 and 2 changes',
331
- suggestion: 'Specify API contracts (OpenAPI, GraphQL, etc.)',
371
+ message: `Contracts required for Tier ${spec.risk_tier} changes`,
372
+ suggestion: suggestion,
332
373
  canAutoFix: false,
374
+ example: isChoreMode
375
+ ? {
376
+ contracts: [
377
+ {
378
+ type: 'project_setup',
379
+ path: '.caws/working-spec.yaml',
380
+ description:
381
+ 'Project-level CAWS configuration. Feature-specific contracts will be added as features are developed.',
382
+ },
383
+ ],
384
+ }
385
+ : {
386
+ contracts: [
387
+ {
388
+ type: 'openapi',
389
+ path: 'docs/api/feature.yaml',
390
+ version: '1.0.0',
391
+ },
392
+ ],
393
+ },
333
394
  });
334
395
  }
335
396
  }
@@ -368,6 +429,48 @@ function validateWorkingSpecWithSuggestions(spec, options = {}) {
368
429
  }
369
430
  }
370
431
 
432
+ // Validate rollback format if present (for all tiers)
433
+ if (spec.rollback !== undefined) {
434
+ if (!Array.isArray(spec.rollback)) {
435
+ errors.push({
436
+ instancePath: '/rollback',
437
+ message: 'rollback must be an array of strings',
438
+ suggestion: 'Use format: ["Step 1", "Step 2", "Step 3"]',
439
+ canAutoFix: false,
440
+ });
441
+ } else {
442
+ // Check for duplicates
443
+ const uniqueSteps = [...new Set(spec.rollback)];
444
+ if (uniqueSteps.length !== spec.rollback.length) {
445
+ warnings.push({
446
+ instancePath: '/rollback',
447
+ message: 'Duplicate entries found in rollback array',
448
+ suggestion: 'Remove duplicate entries',
449
+ });
450
+
451
+ if (autoFix) {
452
+ fixes.push({
453
+ field: 'rollback',
454
+ value: uniqueSteps,
455
+ description: 'Removed duplicate rollback entries',
456
+ reason: 'Duplicate entries detected',
457
+ });
458
+ }
459
+ }
460
+
461
+ // Validate each entry is a string
462
+ const invalidEntries = spec.rollback.filter((entry) => typeof entry !== 'string');
463
+ if (invalidEntries.length > 0) {
464
+ errors.push({
465
+ instancePath: '/rollback',
466
+ message: `Invalid rollback entries (must be strings): ${invalidEntries.length}`,
467
+ suggestion: 'All rollback entries must be string descriptions',
468
+ canAutoFix: false,
469
+ });
470
+ }
471
+ }
472
+ }
473
+
371
474
  // Validate waiver_ids format if present
372
475
  if (spec.waiver_ids) {
373
476
  if (!Array.isArray(spec.waiver_ids)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paths.design/caws-cli",
3
- "version": "7.0.1",
3
+ "version": "7.0.3",
4
4
  "description": "CAWS CLI - Coding Agent Workflow System command line tools",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -13,7 +13,7 @@
13
13
  "templates"
14
14
  ],
15
15
  "scripts": {
16
- "build": "mkdir -p dist && cp -r src/* dist/",
16
+ "build": "mkdir -p dist && cp -r src/* dist/ && cp -r templates dist/ 2>/dev/null || true",
17
17
  "dev": "mkdir -p dist && cp -r src/* dist/ && node dist/index.js",
18
18
  "typecheck": "tsc --emitDeclarationOnly --outDir dist",
19
19
  "start": "node dist/index.js",
@@ -0,0 +1,30 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "CAWS Waivers Configuration",
4
+ "type": "object",
5
+ "patternProperties": {
6
+ ".*": {
7
+ "type": "object",
8
+ "required": ["gate", "reason", "owner", "expiry"],
9
+ "properties": {
10
+ "gate": {
11
+ "type": "string",
12
+ "enum": ["coverage", "mutation", "contracts", "a11y", "perf", "security"]
13
+ },
14
+ "reason": { "type": "string", "minLength": 10 },
15
+ "owner": { "type": "string" },
16
+ "expiry": { "type": "string", "format": "date-time" },
17
+ "compensating_control": { "type": "string" },
18
+ "ticket_url": { "type": "string", "format": "uri" },
19
+ "approved_by": { "type": "string" },
20
+ "created_at": { "type": "string", "format": "date-time" },
21
+ "status": {
22
+ "type": "string",
23
+ "enum": ["active", "expired", "revoked"],
24
+ "default": "active"
25
+ }
26
+ }
27
+ }
28
+ },
29
+ "additionalProperties": false
30
+ }
@@ -0,0 +1,133 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "CAWS Working Spec",
4
+ "type": "object",
5
+ "required": [
6
+ "id",
7
+ "title",
8
+ "risk_tier",
9
+ "mode",
10
+ "blast_radius",
11
+ "operational_rollback_slo",
12
+ "scope",
13
+ "invariants",
14
+ "acceptance",
15
+ "non_functional",
16
+ "contracts"
17
+ ],
18
+ "properties": {
19
+ "id": { "type": "string", "pattern": "^(PROJ|FEAT|FIX|ARCH)-\\d{4}$" },
20
+ "title": { "type": "string", "minLength": 10, "maxLength": 200 },
21
+ "risk_tier": { "type": ["integer", "string"], "enum": [1, 2, 3, "1", "2", "3"] },
22
+ "mode": { "type": "string", "enum": ["feature", "refactor", "fix", "doc", "chore"] },
23
+ "waiver_ids": {
24
+ "type": "array",
25
+ "items": { "type": "string", "pattern": "^WV-\\d{4}$" },
26
+ "description": "IDs of active waivers applying to this spec"
27
+ },
28
+ "blast_radius": {
29
+ "type": "object",
30
+ "required": ["modules"],
31
+ "properties": {
32
+ "modules": { "type": "array", "items": { "type": "string" } },
33
+ "data_migration": { "type": "boolean" }
34
+ }
35
+ },
36
+ "operational_rollback_slo": { "type": "string" },
37
+ "scope": {
38
+ "type": "object",
39
+ "required": ["in", "out"],
40
+ "properties": {
41
+ "in": { "type": "array", "items": { "type": "string" }, "minItems": 1 },
42
+ "out": { "type": "array", "items": { "type": "string" } }
43
+ }
44
+ },
45
+ "invariants": { "type": "array", "items": { "type": "string" }, "minItems": 1 },
46
+ "acceptance": {
47
+ "type": "array",
48
+ "minItems": 1,
49
+ "items": {
50
+ "type": "object",
51
+ "required": ["id", "given", "when", "then"],
52
+ "properties": {
53
+ "id": { "type": "string", "pattern": "^A\\d+$" },
54
+ "given": { "type": "string" },
55
+ "when": { "type": "string" },
56
+ "then": { "type": "string" }
57
+ }
58
+ }
59
+ },
60
+ "non_functional": {
61
+ "type": "object",
62
+ "properties": {
63
+ "a11y": { "type": "array", "items": { "type": "string" } },
64
+ "perf": {
65
+ "type": "object",
66
+ "properties": {
67
+ "api_p95_ms": { "type": "integer", "minimum": 1 },
68
+ "lcp_ms": { "type": "integer", "minimum": 1 }
69
+ },
70
+ "additionalProperties": false
71
+ },
72
+ "security": { "type": "array", "items": { "type": "string" } }
73
+ },
74
+ "additionalProperties": false
75
+ },
76
+ "contracts": {
77
+ "type": "array",
78
+ "minItems": 1,
79
+ "items": {
80
+ "type": "object",
81
+ "required": ["type", "path"],
82
+ "properties": {
83
+ "type": { "type": "string", "enum": ["openapi", "graphql", "proto", "pact"] },
84
+ "path": { "type": "string" }
85
+ }
86
+ }
87
+ },
88
+ "observability": {
89
+ "type": "object",
90
+ "properties": {
91
+ "logs": { "type": "array", "items": { "type": "string" } },
92
+ "metrics": { "type": "array", "items": { "type": "string" } },
93
+ "traces": { "type": "array", "items": { "type": "string" } }
94
+ }
95
+ },
96
+ "migrations": { "type": "array", "items": { "type": "string" } },
97
+ "rollback": { "type": "array", "items": { "type": "string" } },
98
+ "experiment_mode": {
99
+ "type": "boolean",
100
+ "description": "Enables experimental mode with reduced requirements"
101
+ },
102
+ "timeboxed_hours": {
103
+ "type": "integer",
104
+ "minimum": 1,
105
+ "description": "Time limit for experimental features in hours"
106
+ },
107
+ "human_override": {
108
+ "type": "object",
109
+ "properties": {
110
+ "approved_by": { "type": "string" },
111
+ "reason": { "type": "string" },
112
+ "waived_requirements": {
113
+ "type": "array",
114
+ "items": {
115
+ "type": "string",
116
+ "enum": ["mutation_testing", "contract_tests", "coverage", "manual_review"]
117
+ }
118
+ },
119
+ "expiry_date": { "type": "string", "format": "date-time" }
120
+ },
121
+ "required": ["approved_by", "reason"]
122
+ },
123
+ "ai_assessment": {
124
+ "type": "object",
125
+ "properties": {
126
+ "confidence_level": { "type": "integer", "minimum": 1, "maximum": 10 },
127
+ "uncertainty_areas": { "type": "array", "items": { "type": "string" } },
128
+ "recommended_pairing": { "type": "boolean" }
129
+ }
130
+ }
131
+ },
132
+ "additionalProperties": false
133
+ }
@@ -0,0 +1,74 @@
1
+ id: '{{FEATURE_ID}}'
2
+ title: '{{FEATURE_TITLE}}'
3
+ risk_tier: { { TIER } }
4
+ scope:
5
+ in:
6
+ - '{{SCOPE_ITEM_1}}'
7
+ - '{{SCOPE_ITEM_2}}'
8
+ out:
9
+ - '{{OUT_OF_SCOPE_ITEM_1}}'
10
+ invariants:
11
+ - '{{BUSINESS_RULE_1}}'
12
+ - '{{BUSINESS_RULE_2}}'
13
+ acceptance:
14
+ - id: A1
15
+ given: '{{GIVEN_CONDITION}}'
16
+ when: '{{WHEN_ACTION}}'
17
+ then: '{{THEN_OUTCOME}}'
18
+ status: pending # pending | in_progress | completed
19
+ # Optional: detailed progress tracking
20
+ # tests:
21
+ # written: 0
22
+ # passing: 0
23
+ # coverage: 0.0
24
+ # last_updated: '2025-10-09T14:30:00Z'
25
+ - id: A2
26
+ given: '{{GIVEN_CONDITION_2}}'
27
+ when: '{{WHEN_ACTION_2}}'
28
+ then: '{{THEN_OUTCOME_2}}'
29
+ status: pending # pending | in_progress | completed
30
+ # Optional: detailed progress tracking
31
+ # tests:
32
+ # written: 0
33
+ # passing: 0
34
+ # coverage: 0.0
35
+ # last_updated: '2025-10-09T14:30:00Z'
36
+ non_functional:
37
+ a11y:
38
+ - '{{ACCESSIBILITY_REQUIREMENT}}'
39
+ perf:
40
+ api_p95_ms: { { PERF_BUDGET } }
41
+ lcp_ms: { { LCP_BUDGET } }
42
+ security:
43
+ - '{{SECURITY_REQUIREMENT}}'
44
+ contracts:
45
+ - type: '{{CONTRACT_TYPE}}'
46
+ path: '{{CONTRACT_PATH}}'
47
+ observability:
48
+ logs:
49
+ - '{{LOG_STATEMENT}}'
50
+ metrics:
51
+ - '{{METRIC_NAME}}'
52
+ traces:
53
+ - '{{TRACE_SPAN}}'
54
+ migrations:
55
+ - '{{MIGRATION_DESCRIPTION}}'
56
+ rollback:
57
+ - '{{ROLLBACK_STRATEGY}}'
58
+ # Optional: Enable for experimental features with reduced requirements
59
+ # experiment_mode: true
60
+ # timeboxed_hours: 24
61
+
62
+ # Optional: AI confidence assessment
63
+ # ai_assessment:
64
+ # confidence_level: 8
65
+ # uncertainty_areas: ["complex business logic", "performance implications"]
66
+ # recommended_pairing: false
67
+
68
+ # Optional: Human override for special cases (hotfixes, urgent changes)
69
+ # human_override:
70
+ # approved_by: "senior-dev-username"
71
+ # reason: "Urgent production fix - bypassing mutation tests for immediate deployment"
72
+ # waived_requirements: ["mutation_testing", "manual_review"]
73
+ # expiry_date: "2025-10-01T00:00:00Z"
74
+
@@ -0,0 +1,20 @@
1
+ # CAWS Tools
2
+
3
+ This directory contains CAWS-specific tools that aren't available in the CLI.
4
+
5
+ ## scope-guard.js
6
+
7
+ Enforces that experimental code stays within designated sandbox areas. Used by Cursor hooks for scope validation.
8
+
9
+ ```bash
10
+ # Validate experimental code containment
11
+ node .caws/tools/scope-guard.js validate
12
+
13
+ # Check containment status
14
+ node .caws/tools/scope-guard.js check .caws/working-spec.yaml
15
+ ```
16
+
17
+ **Usage in Cursor Hooks:**
18
+
19
+ The `.cursor/hooks/scope-guard.sh` hook automatically uses this tool to validate file attachments against working spec scope boundaries.
20
+
@@ -0,0 +1,208 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @fileoverview CAWS Scope Guard
5
+ * Enforces that experimental code stays within designated sandbox areas
6
+ * @author @darianrosebrook
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const { execSync } = require('child_process');
11
+
12
+ /**
13
+ * Check if experimental code is properly contained
14
+ * @param {string} workingSpecPath - Path to working spec file
15
+ * @returns {Object} Scope validation results
16
+ */
17
+ function checkExperimentalContainment(workingSpecPath = '.caws/working-spec.yaml') {
18
+ try {
19
+ if (!fs.existsSync(workingSpecPath)) {
20
+ console.error('❌ Working spec not found:', workingSpecPath);
21
+ return { valid: false, errors: ['Working spec not found'] };
22
+ }
23
+
24
+ const yaml = require('js-yaml');
25
+ const spec = yaml.load(fs.readFileSync(workingSpecPath, 'utf8'));
26
+
27
+ const results = {
28
+ valid: true,
29
+ errors: [],
30
+ warnings: [],
31
+ experimentalFiles: [],
32
+ nonExperimentalFiles: [],
33
+ };
34
+
35
+ // Only check if experimental mode is enabled
36
+ if (!spec.experimental_mode?.enabled) {
37
+ console.log('ℹ️ Experimental mode not enabled - skipping containment check');
38
+ return results;
39
+ }
40
+
41
+ const sandboxLocation = spec.experimental_mode.sandbox_location || 'experimental/';
42
+ console.log(`🔍 Checking containment for experimental code in: ${sandboxLocation}`);
43
+
44
+ // Get list of changed files (this would typically come from git diff)
45
+ const changedFiles = getChangedFiles();
46
+
47
+ if (changedFiles.length === 0) {
48
+ console.log('ℹ️ No files changed - skipping scope check');
49
+ return results;
50
+ }
51
+
52
+ // Check each changed file
53
+ changedFiles.forEach((file) => {
54
+ const isInSandbox =
55
+ file.startsWith(sandboxLocation) ||
56
+ file.includes(`/${sandboxLocation}`) ||
57
+ file.includes(sandboxLocation);
58
+
59
+ if (isInSandbox) {
60
+ results.experimentalFiles.push(file);
61
+ console.log(`✅ Experimental file properly contained: ${file}`);
62
+ } else {
63
+ results.nonExperimentalFiles.push(file);
64
+ results.valid = false;
65
+ results.errors.push(`Experimental code found outside sandbox: ${file}`);
66
+ console.error(`❌ Experimental code outside sandbox: ${file}`);
67
+ }
68
+ });
69
+
70
+ // Check if experimental files actually exist
71
+ results.experimentalFiles.forEach((file) => {
72
+ if (!fs.existsSync(file)) {
73
+ results.warnings.push(`Experimental file not found (may have been deleted): ${file}`);
74
+ console.warn(`⚠️ Experimental file not found: ${file}`);
75
+ }
76
+ });
77
+
78
+ return results;
79
+ } catch (error) {
80
+ console.error('❌ Error checking experimental containment:', error.message);
81
+ return { valid: false, errors: [error.message] };
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Get list of changed files from git
87
+ * @returns {Array} List of changed file paths
88
+ */
89
+ function getChangedFiles() {
90
+ try {
91
+ // Get files that are staged or modified
92
+ const staged = execSync('git diff --cached --name-only', { encoding: 'utf8' })
93
+ .split('\n')
94
+ .filter((file) => file.trim());
95
+
96
+ const modified = execSync('git diff --name-only', { encoding: 'utf8' })
97
+ .split('\n')
98
+ .filter((file) => file.trim());
99
+
100
+ // Combine and deduplicate
101
+ const allFiles = [...new Set([...staged, ...modified])];
102
+
103
+ // Filter out deleted files (they might still be in the diff)
104
+ return allFiles.filter((file) => {
105
+ try {
106
+ return fs.existsSync(file);
107
+ } catch {
108
+ return false;
109
+ }
110
+ });
111
+ } catch (error) {
112
+ console.warn('⚠️ Could not get changed files from git:', error.message);
113
+ return [];
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Validate that experimental code follows containment rules
119
+ * @param {string} workingSpecPath - Path to working spec file
120
+ */
121
+ function validateExperimentalScope(workingSpecPath = '.caws/working-spec.yaml') {
122
+ console.log('🔍 Validating experimental code containment...');
123
+
124
+ const results = checkExperimentalContainment(workingSpecPath);
125
+
126
+ if (!results.valid) {
127
+ console.error('\n❌ Experimental containment validation failed:');
128
+ results.errors.forEach((error) => {
129
+ console.error(` - ${error}`);
130
+ });
131
+
132
+ if (results.warnings.length > 0) {
133
+ console.warn('\n⚠️ Warnings:');
134
+ results.warnings.forEach((warning) => {
135
+ console.warn(` - ${warning}`);
136
+ });
137
+ }
138
+
139
+ console.error('\n💡 To fix containment issues:');
140
+ console.error(' 1. Move experimental code to the designated sandbox location');
141
+ console.error(' 2. Update the sandbox_location in your working spec');
142
+ console.error(' 3. Or disable experimental mode if this is production code');
143
+
144
+ process.exit(1);
145
+ }
146
+
147
+ if (results.warnings.length > 0) {
148
+ console.warn('\n⚠️ Experimental containment warnings:');
149
+ results.warnings.forEach((warning) => {
150
+ console.warn(` - ${warning}`);
151
+ });
152
+ }
153
+
154
+ console.log('✅ Experimental code containment validated');
155
+ console.log(` - Files in sandbox: ${results.experimentalFiles.length}`);
156
+ console.log(` - Files outside sandbox: ${results.nonExperimentalFiles.length}`);
157
+ }
158
+
159
+ // CLI interface
160
+ if (require.main === module) {
161
+ const command = process.argv[2];
162
+ const specPath = process.argv[3] || '.caws/working-spec.yaml';
163
+
164
+ switch (command) {
165
+ case 'validate':
166
+ validateExperimentalScope(specPath);
167
+ break;
168
+
169
+ case 'check':
170
+ const results = checkExperimentalContainment(specPath);
171
+ console.log('\n📊 Containment Check Results:');
172
+ console.log(` Valid: ${results.valid}`);
173
+ console.log(` Experimental files: ${results.experimentalFiles.length}`);
174
+ console.log(` Non-experimental files: ${results.nonExperimentalFiles.length}`);
175
+ console.log(` Errors: ${results.errors.length}`);
176
+ console.log(` Warnings: ${results.warnings.length}`);
177
+
178
+ if (results.errors.length > 0) {
179
+ console.log('\n❌ Errors:');
180
+ results.errors.forEach((error) => console.log(` - ${error}`));
181
+ }
182
+
183
+ if (results.warnings.length > 0) {
184
+ console.log('\n⚠️ Warnings:');
185
+ results.warnings.forEach((warning) => console.log(` - ${warning}`));
186
+ }
187
+
188
+ process.exit(results.valid ? 0 : 1);
189
+ break;
190
+
191
+ default:
192
+ console.log('CAWS Scope Guard');
193
+ console.log('Usage:');
194
+ console.log(' node scope-guard.js validate [spec-path]');
195
+ console.log(' node scope-guard.js check [spec-path]');
196
+ console.log('');
197
+ console.log('Examples:');
198
+ console.log(' node scope-guard.js validate');
199
+ console.log(' node scope-guard.js check .caws/working-spec.yaml');
200
+ process.exit(1);
201
+ }
202
+ }
203
+
204
+ module.exports = {
205
+ checkExperimentalContainment,
206
+ validateExperimentalScope,
207
+ getChangedFiles,
208
+ };