@cleocode/core 2026.4.11 → 2026.4.12

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 (169) hide show
  1. package/dist/codebase-map/analyzers/architecture.d.ts.map +1 -1
  2. package/dist/codebase-map/analyzers/architecture.js +0 -1
  3. package/dist/codebase-map/analyzers/architecture.js.map +1 -1
  4. package/dist/conduit/local-transport.d.ts +18 -8
  5. package/dist/conduit/local-transport.d.ts.map +1 -1
  6. package/dist/conduit/local-transport.js +23 -13
  7. package/dist/conduit/local-transport.js.map +1 -1
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +0 -1
  10. package/dist/config.js.map +1 -1
  11. package/dist/errors.d.ts +19 -0
  12. package/dist/errors.d.ts.map +1 -1
  13. package/dist/errors.js +6 -0
  14. package/dist/errors.js.map +1 -1
  15. package/dist/index.js +175 -68950
  16. package/dist/index.js.map +1 -7
  17. package/dist/init.d.ts +1 -2
  18. package/dist/init.d.ts.map +1 -1
  19. package/dist/init.js +1 -2
  20. package/dist/init.js.map +1 -1
  21. package/dist/internal.d.ts +8 -3
  22. package/dist/internal.d.ts.map +1 -1
  23. package/dist/internal.js +13 -6
  24. package/dist/internal.js.map +1 -1
  25. package/dist/memory/learnings.d.ts +2 -2
  26. package/dist/memory/patterns.d.ts +6 -6
  27. package/dist/output.d.ts +32 -11
  28. package/dist/output.d.ts.map +1 -1
  29. package/dist/output.js +67 -67
  30. package/dist/output.js.map +1 -1
  31. package/dist/paths.js +80 -14
  32. package/dist/paths.js.map +1 -1
  33. package/dist/skills/dynamic-skill-generator.d.ts +0 -2
  34. package/dist/skills/dynamic-skill-generator.d.ts.map +1 -1
  35. package/dist/skills/dynamic-skill-generator.js.map +1 -1
  36. package/dist/store/agent-registry-accessor.d.ts +203 -12
  37. package/dist/store/agent-registry-accessor.d.ts.map +1 -1
  38. package/dist/store/agent-registry-accessor.js +618 -100
  39. package/dist/store/agent-registry-accessor.js.map +1 -1
  40. package/dist/store/api-key-kdf.d.ts +73 -0
  41. package/dist/store/api-key-kdf.d.ts.map +1 -0
  42. package/dist/store/api-key-kdf.js +84 -0
  43. package/dist/store/api-key-kdf.js.map +1 -0
  44. package/dist/store/cleanup-legacy.js +171 -0
  45. package/dist/store/cleanup-legacy.js.map +1 -0
  46. package/dist/store/conduit-sqlite.d.ts +184 -0
  47. package/dist/store/conduit-sqlite.d.ts.map +1 -0
  48. package/dist/store/conduit-sqlite.js +570 -0
  49. package/dist/store/conduit-sqlite.js.map +1 -0
  50. package/dist/store/global-salt.d.ts +78 -0
  51. package/dist/store/global-salt.d.ts.map +1 -0
  52. package/dist/store/global-salt.js +147 -0
  53. package/dist/store/global-salt.js.map +1 -0
  54. package/dist/store/migrate-signaldock-to-conduit.d.ts +81 -0
  55. package/dist/store/migrate-signaldock-to-conduit.d.ts.map +1 -0
  56. package/dist/store/migrate-signaldock-to-conduit.js +555 -0
  57. package/dist/store/migrate-signaldock-to-conduit.js.map +1 -0
  58. package/dist/store/nexus-sqlite.js +28 -3
  59. package/dist/store/nexus-sqlite.js.map +1 -1
  60. package/dist/store/signaldock-sqlite.d.ts +122 -19
  61. package/dist/store/signaldock-sqlite.d.ts.map +1 -1
  62. package/dist/store/signaldock-sqlite.js +401 -251
  63. package/dist/store/signaldock-sqlite.js.map +1 -1
  64. package/dist/store/sqlite-backup.js +122 -4
  65. package/dist/store/sqlite-backup.js.map +1 -1
  66. package/dist/system/backup.d.ts +0 -26
  67. package/dist/system/backup.d.ts.map +1 -1
  68. package/dist/system/runtime.d.ts +0 -2
  69. package/dist/system/runtime.d.ts.map +1 -1
  70. package/dist/system/runtime.js +3 -3
  71. package/dist/system/runtime.js.map +1 -1
  72. package/dist/tasks/add.d.ts +1 -1
  73. package/dist/tasks/add.d.ts.map +1 -1
  74. package/dist/tasks/add.js +98 -23
  75. package/dist/tasks/add.js.map +1 -1
  76. package/dist/tasks/complete.d.ts.map +1 -1
  77. package/dist/tasks/complete.js +4 -1
  78. package/dist/tasks/complete.js.map +1 -1
  79. package/dist/tasks/find.d.ts.map +1 -1
  80. package/dist/tasks/find.js +4 -1
  81. package/dist/tasks/find.js.map +1 -1
  82. package/dist/tasks/labels.d.ts.map +1 -1
  83. package/dist/tasks/labels.js +4 -1
  84. package/dist/tasks/labels.js.map +1 -1
  85. package/dist/tasks/relates.d.ts.map +1 -1
  86. package/dist/tasks/relates.js +16 -4
  87. package/dist/tasks/relates.js.map +1 -1
  88. package/dist/tasks/show.d.ts.map +1 -1
  89. package/dist/tasks/show.js +4 -1
  90. package/dist/tasks/show.js.map +1 -1
  91. package/dist/tasks/update.d.ts.map +1 -1
  92. package/dist/tasks/update.js +32 -6
  93. package/dist/tasks/update.js.map +1 -1
  94. package/dist/validation/engine.d.ts.map +1 -1
  95. package/dist/validation/engine.js +16 -4
  96. package/dist/validation/engine.js.map +1 -1
  97. package/dist/validation/param-utils.d.ts +5 -3
  98. package/dist/validation/param-utils.d.ts.map +1 -1
  99. package/dist/validation/param-utils.js +8 -6
  100. package/dist/validation/param-utils.js.map +1 -1
  101. package/dist/validation/protocols/_shared.d.ts.map +1 -1
  102. package/dist/validation/protocols/_shared.js +13 -6
  103. package/dist/validation/protocols/_shared.js.map +1 -1
  104. package/package.json +7 -7
  105. package/src/adapters/__tests__/manager.test.ts +0 -1
  106. package/src/codebase-map/analyzers/architecture.ts +0 -1
  107. package/src/conduit/__tests__/local-credential-flow.test.ts +20 -18
  108. package/src/conduit/__tests__/local-transport.test.ts +14 -12
  109. package/src/conduit/local-transport.ts +23 -13
  110. package/src/config.ts +0 -1
  111. package/src/errors.ts +24 -0
  112. package/src/hooks/handlers/__tests__/hook-automation-e2e.test.ts +2 -5
  113. package/src/init.ts +1 -2
  114. package/src/internal.ts +49 -2
  115. package/src/lifecycle/cant/lifecycle-rcasd.cant +133 -0
  116. package/src/memory/__tests__/engine-compat.test.ts +2 -2
  117. package/src/memory/__tests__/pipeline-manifest-sqlite.test.ts +4 -4
  118. package/src/observability/__tests__/index.test.ts +4 -4
  119. package/src/observability/__tests__/log-filter.test.ts +4 -4
  120. package/src/output.ts +73 -75
  121. package/src/sessions/__tests__/session-grade.integration.test.ts +1 -1
  122. package/src/sessions/__tests__/session-grade.test.ts +2 -2
  123. package/src/skills/__tests__/dynamic-skill-generator.test.ts +0 -2
  124. package/src/skills/dynamic-skill-generator.ts +0 -2
  125. package/src/store/__tests__/agent-registry-accessor.test.ts +807 -0
  126. package/src/store/__tests__/api-key-kdf.test.ts +113 -0
  127. package/src/store/__tests__/conduit-sqlite.test.ts +413 -0
  128. package/src/store/__tests__/global-salt.test.ts +195 -0
  129. package/src/store/__tests__/migrate-signaldock-to-conduit.test.ts +715 -0
  130. package/src/store/__tests__/signaldock-sqlite.test.ts +652 -0
  131. package/src/store/__tests__/sqlite-backup-global.test.ts +307 -3
  132. package/src/store/__tests__/sqlite-backup.test.ts +5 -1
  133. package/src/store/__tests__/t310-integration.test.ts +1150 -0
  134. package/src/store/agent-registry-accessor.ts +847 -140
  135. package/src/store/api-key-kdf.ts +104 -0
  136. package/src/store/conduit-sqlite.ts +655 -0
  137. package/src/store/global-salt.ts +175 -0
  138. package/src/store/migrate-signaldock-to-conduit.ts +669 -0
  139. package/src/store/signaldock-sqlite.ts +431 -254
  140. package/src/store/sqlite-backup.ts +185 -10
  141. package/src/system/backup.ts +2 -62
  142. package/src/system/runtime.ts +4 -6
  143. package/src/tasks/__tests__/error-hints.test.ts +256 -0
  144. package/src/tasks/add.ts +99 -9
  145. package/src/tasks/complete.ts +4 -1
  146. package/src/tasks/find.ts +4 -1
  147. package/src/tasks/labels.ts +4 -1
  148. package/src/tasks/relates.ts +16 -4
  149. package/src/tasks/show.ts +4 -1
  150. package/src/tasks/update.ts +32 -3
  151. package/src/validation/__tests__/error-hints.test.ts +97 -0
  152. package/src/validation/engine.ts +16 -1
  153. package/src/validation/param-utils.ts +10 -7
  154. package/src/validation/protocols/_shared.ts +14 -6
  155. package/src/validation/protocols/cant/architecture-decision.cant +80 -0
  156. package/src/validation/protocols/cant/artifact-publish.cant +95 -0
  157. package/src/validation/protocols/cant/consensus.cant +74 -0
  158. package/src/validation/protocols/cant/contribution.cant +82 -0
  159. package/src/validation/protocols/cant/decomposition.cant +92 -0
  160. package/src/validation/protocols/cant/implementation.cant +67 -0
  161. package/src/validation/protocols/cant/provenance.cant +88 -0
  162. package/src/validation/protocols/cant/release.cant +96 -0
  163. package/src/validation/protocols/cant/research.cant +66 -0
  164. package/src/validation/protocols/cant/specification.cant +67 -0
  165. package/src/validation/protocols/cant/testing.cant +88 -0
  166. package/src/validation/protocols/cant/validation.cant +65 -0
  167. package/src/validation/protocols/protocols-markdown/decomposition.md +0 -4
  168. package/templates/config.template.json +0 -1
  169. package/templates/global-config.template.json +0 -1
package/src/errors.ts CHANGED
@@ -77,6 +77,23 @@ function exitCodeToLafsCode(code: ExitCode): string {
77
77
  return `E_${category}_${name}`;
78
78
  }
79
79
 
80
+ /**
81
+ * Structured field-level details for constraint-violation errors.
82
+ * Surfaces in the LAFS envelope so agents can recover programmatically.
83
+ *
84
+ * @task T341
85
+ */
86
+ export interface CleoErrorDetails {
87
+ /** The specific field that failed validation. */
88
+ field: string;
89
+ /** The expected value or constraint (e.g. max length, valid enum members). */
90
+ expected?: unknown;
91
+ /** The actual value that was provided. */
92
+ actual?: unknown;
93
+ /** Additional context keys. */
94
+ [key: string]: unknown;
95
+ }
96
+
80
97
  /**
81
98
  * Structured error class for CLEO operations.
82
99
  * Carries an exit code, human-readable message, and optional fix suggestions.
@@ -87,6 +104,8 @@ export class CleoError extends Error {
87
104
  readonly code: ExitCode;
88
105
  readonly fix?: string;
89
106
  readonly alternatives?: Array<{ action: string; command: string }>;
107
+ /** Field-level details for constraint-violation errors. @task T341 */
108
+ readonly details?: CleoErrorDetails;
90
109
 
91
110
  constructor(
92
111
  code: ExitCode,
@@ -94,6 +113,7 @@ export class CleoError extends Error {
94
113
  options?: {
95
114
  fix?: string;
96
115
  alternatives?: Array<{ action: string; command: string }>;
116
+ details?: CleoErrorDetails;
97
117
  cause?: unknown;
98
118
  },
99
119
  ) {
@@ -102,6 +122,7 @@ export class CleoError extends Error {
102
122
  this.code = code;
103
123
  this.fix = options?.fix;
104
124
  this.alternatives = options?.alternatives;
125
+ this.details = options?.details;
105
126
  }
106
127
 
107
128
  /**
@@ -121,6 +142,7 @@ export class CleoError extends Error {
121
142
  name: getExitCodeName(this.code),
122
143
  ...(this.fix && { fix: this.fix }),
123
144
  ...(this.alternatives && { alternatives: this.alternatives }),
145
+ ...(this.details && { fieldDetails: this.details }),
124
146
  },
125
147
  };
126
148
  }
@@ -145,6 +167,7 @@ export class CleoError extends Error {
145
167
  recoverable: isRecoverableCode(this.code),
146
168
  ...(this.fix && { fix: this.fix }),
147
169
  ...(this.alternatives && { alternatives: this.alternatives }),
170
+ ...(this.details && { fieldDetails: this.details }),
148
171
  },
149
172
  };
150
173
  }
@@ -159,6 +182,7 @@ export class CleoError extends Error {
159
182
  message: this.message,
160
183
  ...(this.fix && { fix: this.fix }),
161
184
  ...(this.alternatives && { alternatives: this.alternatives }),
185
+ ...(this.details && { details: this.details }),
162
186
  },
163
187
  };
164
188
  }
@@ -59,7 +59,6 @@ function makeConfig(
59
59
  autoCapture?: boolean;
60
60
  captureWork?: boolean;
61
61
  captureFiles?: boolean;
62
- captureMcp?: boolean;
63
62
  autoRefresh?: boolean;
64
63
  } = {},
65
64
  ): Awaited<ReturnType<typeof configModule.loadConfig>> {
@@ -68,7 +67,6 @@ function makeConfig(
68
67
  autoCapture: overrides.autoCapture ?? true,
69
68
  captureWork: overrides.captureWork ?? false,
70
69
  captureFiles: overrides.captureFiles ?? false,
71
- captureMcp: overrides.captureMcp ?? false,
72
70
  memoryBridge: { autoRefresh: overrides.autoRefresh ?? false },
73
71
  embedding: { enabled: false, provider: 'local' },
74
72
  summarization: { enabled: false },
@@ -564,9 +562,8 @@ describe('hook automation E2E', () => {
564
562
  );
565
563
  });
566
564
 
567
- it('work-capture and mcp-hooks register on same event but use different config keys', async () => {
568
- // work-capture is keyed on captureWork; mcp-hooks keyed on captureMcp
569
- // When both are disabled (default), neither fires
565
+ it('work-capture does not fire when captureWork is disabled (default)', async () => {
566
+ // When captureWork is disabled (default), work-capture does not fire
570
567
  await handleWorkPromptSubmit(PROJECT_ROOT, {
571
568
  timestamp: TIMESTAMP,
572
569
  gateway: 'mutate',
package/src/init.ts CHANGED
@@ -11,8 +11,7 @@
11
11
  * 4. Sequence counter (SQLite schema_meta)
12
12
  * 5. Project info (.cleo/project-info.json)
13
13
  * 6. CAAMP injection into agent instruction files (AGENTS.md hub pattern)
14
- * 7. (removed was MCP server installation)
15
- * 8. Agent definition installation (cleo-subagent)
14
+ * 7. Agent definition installation (cleo-subagent)
16
15
  * 9. Core skill installation via CAAMP
17
16
  * 10. NEXUS project registration
18
17
  * 11. Project type detection (--detect)
package/src/internal.ts CHANGED
@@ -422,17 +422,31 @@ export {
422
422
  isCleoGitInitialized,
423
423
  } from './store/git-checkpoint.js';
424
424
  export { computeChecksum, readJson } from './store/json.js';
425
+ export type { MigrationResult } from './store/migrate-signaldock-to-conduit.js';
426
+ export {
427
+ migrateSignaldockToConduit,
428
+ needsSignaldockToConduitMigration,
429
+ } from './store/migrate-signaldock-to-conduit.js';
425
430
  export { createSession, getActiveSession } from './store/session-store.js';
426
431
  export {
432
+ _resetGlobalSignaldockDb_TESTING_ONLY,
433
+ checkGlobalSignaldockDbHealth,
427
434
  checkSignaldockDbHealth,
435
+ ensureGlobalSignaldockDb,
428
436
  ensureSignaldockDb,
437
+ GLOBAL_SIGNALDOCK_DB_FILENAME,
438
+ GLOBAL_SIGNALDOCK_SCHEMA_VERSION,
439
+ getGlobalSignaldockDbPath,
440
+ getGlobalSignaldockNativeDb,
429
441
  getSignaldockDbPath,
430
442
  SIGNALDOCK_SCHEMA_VERSION,
431
443
  } from './store/signaldock-sqlite.js';
432
444
  export { getDb, getNativeDb } from './store/sqlite.js';
433
- export type { BackupScope, GlobalBackupEntry } from './store/sqlite-backup.js';
445
+ export type { BackupScope, GlobalBackupEntry, GlobalSaltBackupEntry } from './store/sqlite-backup.js';
434
446
  export {
447
+ backupGlobalSalt,
435
448
  listBrainBackups,
449
+ listGlobalSaltBackups,
436
450
  listGlobalSqliteBackups,
437
451
  listSqliteBackups,
438
452
  listSqliteBackupsAll,
@@ -589,6 +603,7 @@ export type {
589
603
  export {
590
604
  buildCommanderArgs,
591
605
  buildCommanderOptionString,
606
+ buildDispatchInputSchema,
592
607
  buildMcpInputSchema,
593
608
  camelToKebab,
594
609
  validateRequiredParamsDef,
@@ -847,6 +862,30 @@ export { createSqliteDataAccessor } from './store/sqlite-data-accessor.js';
847
862
  // Validation — doctor checks (used by cleo init tests)
848
863
  export { checkRootGitignore } from './validation/doctor/checks.js';
849
864
 
865
+ // ---------------------------------------------------------------------------
866
+ // T310 startup sequence exports (required by cli/index.ts T360 wire-up)
867
+ // ---------------------------------------------------------------------------
868
+
869
+ // Conduit DB lifecycle — ensureConduitDb is called at every CLI startup (step 3)
870
+ export type { ProjectAgentRef } from './store/conduit-sqlite.js';
871
+ export {
872
+ CONDUIT_DB_FILENAME,
873
+ CONDUIT_SCHEMA_VERSION,
874
+ checkConduitDbHealth,
875
+ ensureConduitDb,
876
+ getConduitDbPath,
877
+ getConduitNativeDb,
878
+ } from './store/conduit-sqlite.js';
879
+
880
+ // Global-salt lifecycle — validateGlobalSalt and getGlobalSalt used at startup (step 5)
881
+ export {
882
+ GLOBAL_SALT_FILENAME,
883
+ GLOBAL_SALT_SIZE,
884
+ getGlobalSalt,
885
+ getGlobalSaltPath,
886
+ validateGlobalSalt,
887
+ } from './store/global-salt.js';
888
+
850
889
  // ---------------------------------------------------------------------------
851
890
  // Agent Registry + Conduit (T170 Unification)
852
891
  // ---------------------------------------------------------------------------
@@ -855,4 +894,12 @@ export { ConduitClient } from './conduit/conduit-client.js';
855
894
  export { createConduit } from './conduit/factory.js';
856
895
  export { HttpTransport } from './conduit/http-transport.js';
857
896
  export { decrypt, encrypt } from './crypto/credentials.js';
858
- export { AgentRegistryAccessor } from './store/agent-registry-accessor.js';
897
+ export {
898
+ AgentRegistryAccessor,
899
+ attachAgentToProject,
900
+ createProjectAgent,
901
+ detachAgentFromProject,
902
+ getProjectAgentRef,
903
+ listAgentsForProject,
904
+ lookupAgent,
905
+ } from './store/agent-registry-accessor.js';
@@ -0,0 +1,133 @@
1
+ ---
2
+ kind: lifecycle
3
+ version: 1.0.0
4
+ title: "RCASD-IVTR+C Pipeline Stages"
5
+ status: active
6
+ ---
7
+
8
+ # RCASD-IVTR+C Pipeline Stage Definitions
9
+ #
10
+ # Canonical pipeline stages in execution order.
11
+ # Source of truth: packages/core/src/lifecycle/stages.ts
12
+ #
13
+ # Stages: research, consensus, architecture_decision, specification,
14
+ # decomposition, implementation, validation, testing, release
15
+ #
16
+ # Cross-cutting stage: contribution (not part of pipeline execution order,
17
+ # tracked for attribution and provenance recording)
18
+ #
19
+ # Stage 1: Research
20
+ # order: 1
21
+ # category: planning
22
+ # description: Information gathering, exploration, and knowledge acquisition
23
+ # skippable: false
24
+ # defaultTimeoutHours: 48
25
+ # requiredGates: prerequisites-met
26
+ # expectedArtifacts: research-report, findings-document
27
+ #
28
+ # Stage 2: Consensus
29
+ # order: 2
30
+ # category: decision
31
+ # description: Multi-agent decision making and validation of research findings
32
+ # skippable: true
33
+ # defaultTimeoutHours: 24
34
+ # requiredGates: research-complete, agreement-reached
35
+ # expectedArtifacts: consensus-record, decision-log
36
+ #
37
+ # Stage 3: Architecture Decision
38
+ # order: 3
39
+ # category: decision
40
+ # description: Architecture Decision Records - documenting significant technical decisions
41
+ # skippable: true
42
+ # defaultTimeoutHours: 24
43
+ # requiredGates: decisions-documented, review-completed
44
+ # expectedArtifacts: adr-document
45
+ #
46
+ # Stage 4: Specification
47
+ # order: 4
48
+ # category: planning
49
+ # description: Specification writing - RFC-style documentation of requirements and design
50
+ # skippable: false
51
+ # defaultTimeoutHours: 72
52
+ # requiredGates: spec-complete, spec-reviewed
53
+ # expectedArtifacts: spec-document, api-spec, design-doc
54
+ #
55
+ # Stage 5: Decomposition
56
+ # order: 5
57
+ # category: planning
58
+ # description: Task breakdown - splitting work into atomic, executable tasks
59
+ # skippable: false
60
+ # defaultTimeoutHours: 24
61
+ # requiredGates: tasks-created, dependencies-mapped
62
+ # expectedArtifacts: task-breakdown, dependency-graph
63
+ #
64
+ # Stage 6: Implementation
65
+ # order: 6
66
+ # category: execution
67
+ # description: Implementation - writing code and building features
68
+ # skippable: false
69
+ # defaultTimeoutHours: none
70
+ # requiredGates: code-complete, lint-passing
71
+ # expectedArtifacts: source-code, implementation-notes
72
+ #
73
+ # Stage 7: Validation
74
+ # order: 7
75
+ # category: validation
76
+ # description: Verification - static analysis, type checking, and quality gates
77
+ # skippable: false
78
+ # defaultTimeoutHours: 12
79
+ # requiredGates: static-analysis-pass, type-check-pass
80
+ # expectedArtifacts: verification-report
81
+ #
82
+ # Stage 8: Testing
83
+ # order: 8
84
+ # category: validation
85
+ # description: Testing - running test suites and ensuring coverage
86
+ # skippable: false
87
+ # defaultTimeoutHours: 24
88
+ # requiredGates: tests-pass, coverage-met
89
+ # expectedArtifacts: test-results, coverage-report
90
+ #
91
+ # Stage 9: Release
92
+ # order: 9
93
+ # category: delivery
94
+ # description: Release - versioning, publishing, and deployment
95
+ # skippable: true
96
+ # defaultTimeoutHours: 4
97
+ # requiredGates: version-bumped, changelog-updated, artifacts-published
98
+ # expectedArtifacts: version-tag, release-notes, published-package
99
+ #
100
+ # Stage Categories:
101
+ # planning: research, specification, decomposition
102
+ # decision: consensus, architecture_decision
103
+ # execution: implementation
104
+ # validation: validation, testing
105
+ # delivery: release
106
+ #
107
+ # Prerequisites:
108
+ # research: (none)
109
+ # consensus: research
110
+ # architecture_decision: research, consensus
111
+ # specification: research, consensus, architecture_decision
112
+ # decomposition: research, specification
113
+ # implementation: research, specification, decomposition
114
+ # validation: implementation
115
+ # testing: implementation, validation
116
+ # release: implementation, validation, testing
117
+ #
118
+ # Transition Rules:
119
+ # Forward progressions (always allowed):
120
+ # research -> consensus -> architecture_decision -> specification
121
+ # specification -> decomposition -> implementation -> validation
122
+ # validation -> testing -> release
123
+ # Skip patterns (allowed with force):
124
+ # research -> specification (skipping consensus + architecture_decision)
125
+ # specification -> implementation (skipping decomposition)
126
+ # Backward transitions (allowed with force, for rework):
127
+ # implementation -> specification (rework required)
128
+ # testing -> implementation (fix test failures)
129
+ # Disallowed:
130
+ # release -> any (pipeline completed)
131
+ #
132
+ # Stage Status Values:
133
+ # not_started, in_progress, completed, skipped, blocked, failed
@@ -37,7 +37,7 @@ const SAMPLE_ENTRIES = [
37
37
  date: '2026-01-15',
38
38
  status: 'completed',
39
39
  agent_type: 'research',
40
- topics: ['mcp', 'engine'],
40
+ topics: ['async-ops', 'engine'],
41
41
  key_findings: ['finding1', 'finding2', 'finding3'],
42
42
  actionable: true,
43
43
  linked_tasks: ['T001'],
@@ -186,7 +186,7 @@ describe('Pipeline Manifest SQLite (moved from memory domain)', () => {
186
186
 
187
187
  it('should search by topic', async () => {
188
188
  await seedEntries();
189
- const result = await pipelineManifestFind('mcp', {}, testRoot);
189
+ const result = await pipelineManifestFind('async-ops', {}, testRoot);
190
190
  expect(result.success).toBe(true);
191
191
  expect((result.data as any).total).toBeGreaterThan(0);
192
192
  });
@@ -45,8 +45,8 @@ const ENTRY_A: ExtendedManifestEntry = {
45
45
  date: '2026-01-15',
46
46
  status: 'completed',
47
47
  agent_type: 'research',
48
- topics: ['mcp', 'engine'],
49
- key_findings: ['This library is deprecated', 'MCP supports structured output'],
48
+ topics: ['async-ops', 'engine'],
49
+ key_findings: ['This library is deprecated', 'System supports structured output'],
50
50
  actionable: true,
51
51
  linked_tasks: ['T001'],
52
52
  needs_followup: [],
@@ -464,9 +464,9 @@ describe('pipeline-manifest-sqlite', () => {
464
464
 
465
465
  it('should filter by topic param', async () => {
466
466
  await seedEntries(testRoot, [ENTRY_A, ENTRY_C]);
467
- const result = await pipelineManifestContradictions(testRoot, { topic: 'mcp' });
467
+ const result = await pipelineManifestContradictions(testRoot, { topic: 'async-ops' });
468
468
  expect(result.success).toBe(true);
469
- // ENTRY_C doesn't have 'mcp' topic, so no contradictions on that topic
469
+ // ENTRY_C doesn't have 'async-ops' topic, so no contradictions on that topic
470
470
  expect((result.data as any).contradictions.length).toBe(0);
471
471
  });
472
472
  });
@@ -43,7 +43,7 @@ describe('queryLogs', () => {
43
43
  pid: 1,
44
44
  hostname: 'h',
45
45
  msg: 'Start',
46
- subsystem: 'mcp',
46
+ subsystem: 'engine',
47
47
  }),
48
48
  JSON.stringify({
49
49
  level: 'WARN',
@@ -273,7 +273,7 @@ describe('getLogSummary', () => {
273
273
  pid: 1,
274
274
  hostname: 'h',
275
275
  msg: 'a',
276
- subsystem: 'mcp',
276
+ subsystem: 'engine',
277
277
  }),
278
278
  JSON.stringify({
279
279
  level: 'INFO',
@@ -281,7 +281,7 @@ describe('getLogSummary', () => {
281
281
  pid: 2,
282
282
  hostname: 'h',
283
283
  msg: 'b',
284
- subsystem: 'mcp',
284
+ subsystem: 'engine',
285
285
  }),
286
286
  JSON.stringify({
287
287
  level: 'WARN',
@@ -305,7 +305,7 @@ describe('getLogSummary', () => {
305
305
  const summary = getLogSummary({ scope: 'project' }, tmpDir);
306
306
  expect(summary.totalEntries).toBe(4);
307
307
  expect(summary.byLevel).toEqual({ INFO: 2, WARN: 1, ERROR: 1 });
308
- expect(summary.bySubsystem).toEqual({ mcp: 2, engine: 2 });
308
+ expect(summary.bySubsystem).toEqual({ engine: 4 });
309
309
  expect(summary.dateRange).not.toBeNull();
310
310
  expect(summary.dateRange!.earliest).toBe('2026-02-28T10:00:00Z');
311
311
  expect(summary.dateRange!.latest).toBe('2026-02-28T10:03:00Z');
@@ -73,7 +73,7 @@ describe('matchesFilter', () => {
73
73
  it('filters by subsystem', () => {
74
74
  const entry = makeEntry({ subsystem: 'engine' });
75
75
  expect(matchesFilter(entry, { subsystem: 'engine' })).toBe(true);
76
- expect(matchesFilter(entry, { subsystem: 'mcp' })).toBe(false);
76
+ expect(matchesFilter(entry, { subsystem: 'dispatch' })).toBe(false);
77
77
  });
78
78
 
79
79
  it('filters by code', () => {
@@ -105,7 +105,7 @@ describe('matchesFilter', () => {
105
105
  it('applies AND logic for multiple criteria', () => {
106
106
  const entry = makeEntry({ level: 'ERROR', subsystem: 'engine', code: 'E_NOT_FOUND' });
107
107
  expect(matchesFilter(entry, { level: 'ERROR', subsystem: 'engine' })).toBe(true);
108
- expect(matchesFilter(entry, { level: 'ERROR', subsystem: 'mcp' })).toBe(false);
108
+ expect(matchesFilter(entry, { level: 'ERROR', subsystem: 'dispatch' })).toBe(false);
109
109
  expect(matchesFilter(entry, { level: 'WARN', subsystem: 'engine' })).toBe(false);
110
110
  });
111
111
 
@@ -119,10 +119,10 @@ describe('matchesFilter', () => {
119
119
 
120
120
  describe('filterEntries', () => {
121
121
  const entries = [
122
- makeEntry({ level: 'INFO', msg: 'Starting server', subsystem: 'mcp' }),
122
+ makeEntry({ level: 'INFO', msg: 'Starting server', subsystem: 'dispatch' }),
123
123
  makeEntry({ level: 'WARN', msg: 'Task not found', subsystem: 'engine', code: 'E_NOT_FOUND' }),
124
124
  makeEntry({ level: 'ERROR', msg: 'Database locked', subsystem: 'engine', exitCode: 3 }),
125
- makeEntry({ level: 'INFO', msg: 'Server stopped', subsystem: 'mcp' }),
125
+ makeEntry({ level: 'INFO', msg: 'Server stopped', subsystem: 'dispatch' }),
126
126
  ];
127
127
 
128
128
  it('returns all entries for empty filter', () => {
package/src/output.ts CHANGED
@@ -4,22 +4,28 @@
4
4
  * LAFS (LLM-Agent-First Schema) ensures all CLI output is
5
5
  * machine-parseable JSON by default, with optional human-readable modes.
6
6
  *
7
- * All envelopes are now full LAFS-compliant with $schema and _meta.
8
- * The backward-compatible shape (success + data, no _meta) has been removed.
7
+ * All envelopes use the canonical CLI envelope shape:
8
+ * { success, data?, error?, meta, page? }
9
+ *
10
+ * This replaces the three legacy shapes:
11
+ * {ok, r, _m} (minimal MVI — removed)
12
+ * {$schema, _meta, success, result} (full LAFS — now uses meta/data)
13
+ * {success, result} (observe command — now uses data)
9
14
  *
10
15
  * Types are re-exported from the canonical source in src/types/lafs.ts.
11
16
  *
12
17
  * @epic T4663
13
18
  * @task T4672
19
+ * @task T338 (ADR-039 envelope unification)
14
20
  */
15
21
 
16
22
  import { randomUUID } from 'node:crypto';
17
23
  import type { LafsEnvelope, LafsError, LafsSuccess } from '@cleocode/contracts';
18
- import type { LAFSMeta, LAFSPage, Warning } from '@cleocode/lafs';
24
+ import type { CliEnvelope, CliEnvelopeError, CliMeta, LAFSPage, Warning } from '@cleocode/lafs';
19
25
  import { CleoError } from './errors.js';
20
26
  import { getCurrentSessionId } from './sessions/context-alert.js';
21
27
 
22
- export type { LafsEnvelope, LafsError, LafsSuccess };
28
+ export type { CliEnvelope, CliEnvelopeError, CliMeta, LafsEnvelope, LafsError, LafsSuccess };
23
29
 
24
30
  /**
25
31
  * Accumulated warnings for the current request.
@@ -70,47 +76,57 @@ export interface FormatOptions {
70
76
  }
71
77
 
72
78
  /**
73
- * Create a LAFS-conformant _meta object for CLI envelopes.
79
+ * Create a canonical `CliMeta` object for CLI envelopes.
80
+ *
74
81
  * Includes sessionId (T4702) and warnings (T4669) when present.
82
+ * Drains the pending warnings queue so they are included in the current envelope.
83
+ *
84
+ * @param operation - Dot-delimited operation identifier (e.g. `"tasks.show"`).
85
+ * @param duration_ms - Wall-clock duration in milliseconds. Defaults to 0.
86
+ * @returns A fully populated {@link CliMeta} object.
75
87
  *
76
88
  * @task T4700
77
89
  * @task T4702
90
+ * @task T338
78
91
  * @epic T4663
79
92
  */
80
- function createCliMeta(
81
- operation: string,
82
- mvi: import('@cleocode/lafs').MVILevel = 'minimal',
83
- ): LAFSMeta {
93
+ function createCliMeta(operation: string, duration_ms = 0): CliMeta {
84
94
  const warnings = drainWarnings();
85
- const meta: LAFSMeta = {
86
- specVersion: '1.2.3',
87
- schemaVersion: '2026.2.1',
88
- timestamp: new Date().toISOString(),
95
+ const meta: CliMeta = {
89
96
  operation,
90
97
  requestId: randomUUID(),
91
- transport: 'cli',
92
- strict: true,
93
- mvi,
94
- contextVersion: 1,
95
- ...(warnings && { warnings }),
98
+ duration_ms,
99
+ timestamp: new Date().toISOString(),
96
100
  };
97
101
  const sessionId = getCurrentSessionId();
98
102
  if (sessionId) {
99
- meta.sessionId = sessionId;
103
+ meta['sessionId'] = sessionId;
104
+ }
105
+ if (warnings && warnings.length > 0) {
106
+ meta['warnings'] = warnings;
100
107
  }
101
108
  return meta;
102
109
  }
103
110
 
104
111
  /**
105
- * Format a successful result as a full LAFS-conformant envelope.
112
+ * Format a successful result as a canonical CLI envelope.
113
+ *
114
+ * Produces the unified `CliEnvelope<T>` shape: `{success, data, meta, page?}`.
115
+ * This replaces all three legacy shapes (minimal `{ok,r,_m}`, full `{$schema,_meta,result}`,
116
+ * and observe `{success,result}`) with a single canonical format (ADR-039).
117
+ *
118
+ * The `mvi` option in `FormatOptions` is accepted for backward compatibility but
119
+ * no longer affects the envelope shape — the canonical shape is always emitted.
106
120
  *
107
- * Always produces the full LAFSEnvelope with $schema and _meta.
108
- * When operation is omitted, defaults to 'cli.output'.
109
- * Supports optional page (T4668) and _extensions (T4670).
121
+ * @param data - The operation result payload.
122
+ * @param message - Optional success message (attached to `meta.message`).
123
+ * @param operationOrOpts - Operation name string or `FormatOptions` object.
124
+ * @returns JSON-serialized `CliEnvelope<T>`.
110
125
  *
111
126
  * @task T4672
112
127
  * @task T4668
113
128
  * @task T4670
129
+ * @task T338
114
130
  * @epic T4663
115
131
  */
116
132
  export function formatSuccess<T>(
@@ -121,71 +137,53 @@ export function formatSuccess<T>(
121
137
  const opts: FormatOptions =
122
138
  typeof operationOrOpts === 'string' ? { operation: operationOrOpts } : (operationOrOpts ?? {});
123
139
 
124
- // Determine MVI level: default is 'minimal' (agent-optimized).
125
- // Only --human flag or explicit mvi='full'/'standard' overrides.
126
- const mviLevel = opts.mvi ?? 'minimal';
140
+ const meta = createCliMeta(opts.operation ?? 'cli.output');
127
141
 
128
- const meta = createCliMeta(opts.operation ?? 'cli.output', mviLevel);
129
- const fullEnvelope: Record<string, unknown> = {
130
- $schema: 'https://lafs.dev/schemas/v1/envelope.schema.json',
131
- _meta: meta,
132
- success: true as const,
133
- result: data as Record<string, unknown> | Record<string, unknown>[] | null,
134
- ...(message && { message }),
142
+ const envelope: CliEnvelope<T> = {
143
+ success: true,
144
+ data,
145
+ meta: message ? { ...meta, message } : meta,
135
146
  ...(opts.page && { page: opts.page }),
136
- ...(opts.extensions &&
137
- Object.keys(opts.extensions).length > 0 && { _extensions: opts.extensions }),
138
147
  };
139
148
 
140
- // For 'full' level, skip projection — return the complete envelope as-is.
141
- // This is the backward-compatible path used by --human and conformance tests.
142
- if (mviLevel === 'full') {
143
- return JSON.stringify(fullEnvelope);
144
- }
145
-
146
- // Apply MVI projection — strips envelope to the declared level.
147
- // 'minimal': { success, result, _meta: { requestId, contextVersion } }
148
- // 'standard': + $schema, timestamp, operation, mvi
149
- if (mviLevel === 'minimal') {
150
- // Inline minimal projection — avoids dynamic import overhead.
151
- // Matches projectMetaMinimal() from @cleocode/lafs/mviProjection.ts
152
- const minimalMeta: Record<string, unknown> = {
153
- op: meta.operation,
154
- rid: meta.requestId,
155
- };
156
- if (meta.sessionId) minimalMeta.sid = meta.sessionId;
157
- if (meta.warnings?.length) minimalMeta.w = meta.warnings;
158
-
159
- const minimal: Record<string, unknown> = {
160
- ok: true,
161
- r: data,
162
- _m: minimalMeta,
163
- };
164
- if (message) minimal.msg = message;
165
- if (opts.page) minimal.p = opts.page;
166
- return JSON.stringify(minimal);
167
- }
168
-
169
- // 'standard' level — use the full envelope structure (current behavior)
170
- return JSON.stringify(fullEnvelope);
149
+ return JSON.stringify(envelope);
171
150
  }
172
151
 
173
152
  /**
174
- * Format an error as a full LAFS-conformant envelope.
153
+ * Format an error as a canonical CLI error envelope.
154
+ *
155
+ * Produces `{success: false, error: CliEnvelopeError, meta: CliMeta}`.
156
+ * Every error envelope now always includes `meta` (ADR-039).
157
+ * When operation is omitted, defaults to `'cli.output'`.
175
158
  *
176
- * Always produces the full LAFSEnvelope with $schema and _meta.
177
- * When operation is omitted, defaults to 'cli.output'.
159
+ * @param error - The `CleoError` to format.
160
+ * @param operation - Optional dot-delimited operation identifier.
161
+ * @returns JSON-serialized error `CliEnvelope`.
178
162
  *
179
163
  * @task T4672
164
+ * @task T338
180
165
  * @epic T4663
181
166
  */
182
167
  export function formatError(error: CleoError, operation?: string): string {
183
- const envelope = {
184
- $schema: 'https://lafs.dev/schemas/v1/envelope.schema.json',
185
- _meta: createCliMeta(operation ?? 'cli.output'),
186
- success: false as const,
187
- result: null,
188
- error: error.toLAFSError(),
168
+ const lafsError = error.toLAFSError();
169
+ const errorObj: CliEnvelopeError = {
170
+ code: lafsError.code,
171
+ message: lafsError.message,
172
+ details: lafsError.details,
173
+ };
174
+ if ('category' in lafsError && lafsError.category) {
175
+ (errorObj as Record<string, unknown>)['category'] = lafsError.category;
176
+ }
177
+ if ('retryable' in lafsError) {
178
+ (errorObj as Record<string, unknown>)['retryable'] = lafsError.retryable;
179
+ }
180
+ if ('agentAction' in lafsError && lafsError.agentAction) {
181
+ (errorObj as Record<string, unknown>)['agentAction'] = lafsError.agentAction;
182
+ }
183
+ const envelope: CliEnvelope<null> = {
184
+ success: false,
185
+ error: errorObj,
186
+ meta: createCliMeta(operation ?? 'cli.output'),
189
187
  };
190
188
  return JSON.stringify(envelope);
191
189
  }
@@ -160,7 +160,7 @@ function sloppySession(): AuditEntry[] {
160
160
  params: { title: 'Do something', description: 'Duplicate' },
161
161
  result: { success: true, exitCode: 0, duration: 20 },
162
162
  }),
163
- // No session.end, no help calls, no MCP gateway
163
+ // No session.end, no help calls, no query gateway usage
164
164
  ];
165
165
  }
166
166