@cleocode/contracts 2026.4.96 → 2026.4.98

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 (68) hide show
  1. package/dist/config.d.ts +21 -0
  2. package/dist/config.d.ts.map +1 -1
  3. package/dist/facade.d.ts +64 -2
  4. package/dist/facade.d.ts.map +1 -1
  5. package/dist/facade.js +1 -0
  6. package/dist/facade.js.map +1 -1
  7. package/dist/index.d.ts +3 -2
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/operations/brain.d.ts +579 -0
  11. package/dist/operations/brain.d.ts.map +1 -0
  12. package/dist/operations/brain.js +39 -0
  13. package/dist/operations/brain.js.map +1 -0
  14. package/dist/operations/conduit.d.ts +151 -0
  15. package/dist/operations/conduit.d.ts.map +1 -0
  16. package/dist/operations/conduit.js +29 -0
  17. package/dist/operations/conduit.js.map +1 -0
  18. package/dist/operations/index.d.ts +4 -0
  19. package/dist/operations/index.d.ts.map +1 -1
  20. package/dist/operations/index.js +4 -0
  21. package/dist/operations/index.js.map +1 -1
  22. package/dist/operations/lifecycle.d.ts +29 -1
  23. package/dist/operations/lifecycle.d.ts.map +1 -1
  24. package/dist/operations/memory.d.ts +899 -0
  25. package/dist/operations/memory.d.ts.map +1 -0
  26. package/dist/operations/memory.js +26 -0
  27. package/dist/operations/memory.js.map +1 -0
  28. package/dist/operations/nexus.d.ts +577 -0
  29. package/dist/operations/nexus.d.ts.map +1 -0
  30. package/dist/operations/nexus.js +22 -0
  31. package/dist/operations/nexus.js.map +1 -0
  32. package/dist/operations/orchestrate.d.ts +451 -38
  33. package/dist/operations/orchestrate.d.ts.map +1 -1
  34. package/dist/operations/orchestrate.js +6 -0
  35. package/dist/operations/orchestrate.js.map +1 -1
  36. package/dist/operations/release.d.ts +78 -8
  37. package/dist/operations/release.d.ts.map +1 -1
  38. package/dist/operations/session.d.ts +26 -3
  39. package/dist/operations/session.d.ts.map +1 -1
  40. package/dist/operations/tasks.d.ts +140 -2
  41. package/dist/operations/tasks.d.ts.map +1 -1
  42. package/dist/sentient.d.ts +85 -0
  43. package/dist/sentient.d.ts.map +1 -0
  44. package/dist/sentient.js +13 -0
  45. package/dist/sentient.js.map +1 -0
  46. package/dist/status-registry.d.ts +1 -1
  47. package/dist/status-registry.d.ts.map +1 -1
  48. package/dist/status-registry.js +3 -0
  49. package/dist/status-registry.js.map +1 -1
  50. package/dist/task.d.ts +66 -0
  51. package/dist/task.d.ts.map +1 -1
  52. package/package.json +1 -1
  53. package/src/config.ts +22 -0
  54. package/src/facade.ts +65 -1
  55. package/src/index.ts +12 -0
  56. package/src/operations/brain.ts +635 -0
  57. package/src/operations/conduit.ts +189 -0
  58. package/src/operations/index.ts +4 -0
  59. package/src/operations/lifecycle.ts +29 -1
  60. package/src/operations/memory.ts +1053 -0
  61. package/src/operations/nexus.ts +711 -0
  62. package/src/operations/orchestrate.ts +447 -38
  63. package/src/operations/release.ts +77 -7
  64. package/src/operations/session.ts +26 -3
  65. package/src/operations/tasks.ts +141 -3
  66. package/src/sentient.ts +100 -0
  67. package/src/status-registry.ts +3 -0
  68. package/src/task.ts +75 -0
@@ -29,86 +29,156 @@ export interface ChangelogSection {
29
29
  */
30
30
 
31
31
  // release.prepare
32
+ /**
33
+ * Parameters for `release.prepare`.
34
+ *
35
+ * @remarks
36
+ * Re-synced to match `prepareRelease(version, tasks?, notes?)` in
37
+ * `packages/core/src/release/release-manifest.ts`. The legacy `type` field
38
+ * was never accepted by the engine — the release manifest persists the
39
+ * version as-is after normalization. Task filtering happens via the
40
+ * optional `tasks` array (defaults to all tasks with `status=done`).
41
+ *
42
+ * @task T963 — contract↔impl drift reconciliation (T910 audit)
43
+ */
32
44
  export interface ReleasePrepareParams {
45
+ /** Version string (e.g. `YYYY.M.patch` or `X.Y.Z`). @task T963 */
33
46
  version: string;
34
- type: ReleaseType;
35
- }
47
+ /**
48
+ * Specific task IDs to bundle into the release. When omitted, all
49
+ * completed tasks with `completedAt` are included.
50
+ * @task T963
51
+ */
52
+ tasks?: string[];
53
+ /** Free-form release notes persisted onto the manifest entry. @task T963 */
54
+ notes?: string;
55
+ }
56
+ /** Result of `release.prepare`. @task T963 */
36
57
  export interface ReleasePrepareResult {
58
+ /** Normalized version string. @task T963 */
37
59
  version: string;
38
- type: ReleaseType;
39
- currentVersion: string;
40
- files: string[];
41
- ready: boolean;
42
- warnings: string[];
60
+ /** Manifest status — always `'prepared'` on success. @task T963 */
61
+ status: string;
62
+ /** Task IDs committed to the manifest. @task T963 */
63
+ tasks: string[];
64
+ /** Count of tasks in the release. @task T963 */
65
+ taskCount: number;
43
66
  }
44
67
 
45
68
  // release.changelog
69
+ /** Parameters for `release.changelog`. @task T963 */
46
70
  export interface ReleaseChangelogParams {
71
+ /** Version to build the changelog for (must match an existing manifest). @task T963 */
47
72
  version: string;
73
+ /** Filter emitted sections. @task T963 */
48
74
  sections?: Array<'feat' | 'fix' | 'docs' | 'test' | 'refactor' | 'chore'>;
49
75
  }
76
+ /** Result of `release.changelog`. @task T963 */
50
77
  export interface ReleaseChangelogResult {
78
+ /** Version. @task T963 */
51
79
  version: string;
80
+ /** Rendered changelog content (Markdown). @task T963 */
52
81
  content: string;
82
+ /** Grouped changelog sections. @task T963 */
53
83
  sections: ChangelogSection[];
84
+ /** Count of commits aggregated. @task T963 */
54
85
  commitCount: number;
55
86
  }
56
87
 
57
88
  // release.commit
89
+ /** Parameters for `release.commit`. @task T963 */
58
90
  export interface ReleaseCommitParams {
91
+ /** Version tag being committed. @task T963 */
59
92
  version: string;
93
+ /** Files associated with the commit. @task T963 */
60
94
  files?: string[];
61
95
  }
96
+ /** Result of `release.commit`. @task T963 */
62
97
  export interface ReleaseCommitResult {
98
+ /** Version. @task T963 */
63
99
  version: string;
100
+ /** Git commit hash. @task T963 */
64
101
  commitHash: string;
102
+ /** Commit message. @task T963 */
65
103
  message: string;
104
+ /** Files actually committed. @task T963 */
66
105
  filesCommitted: string[];
67
106
  }
68
107
 
69
108
  // release.tag
109
+ /** Parameters for `release.tag`. @task T963 */
70
110
  export interface ReleaseTagParams {
111
+ /** Version being tagged. @task T963 */
71
112
  version: string;
113
+ /** Tag message (annotated tag). @task T963 */
72
114
  message?: string;
73
115
  }
116
+ /** Result of `release.tag`. @task T963 */
74
117
  export interface ReleaseTagResult {
118
+ /** Version. @task T963 */
75
119
  version: string;
120
+ /** Tag name created. @task T963 */
76
121
  tagName: string;
122
+ /** ISO 8601 creation timestamp. @task T963 */
77
123
  created: string;
78
124
  }
79
125
 
80
126
  // release.push
127
+ /** Parameters for `release.push`. @task T963 */
81
128
  export interface ReleasePushParams {
129
+ /** Version being pushed. @task T963 */
82
130
  version: string;
131
+ /** Git remote name. Defaults to `origin`. @task T963 */
83
132
  remote?: string;
84
133
  }
134
+ /** Result of `release.push`. @task T963 */
85
135
  export interface ReleasePushResult {
136
+ /** Version. @task T963 */
86
137
  version: string;
138
+ /** Remote that received the push. @task T963 */
87
139
  remote: string;
140
+ /** ISO 8601 push timestamp. @task T963 */
88
141
  pushed: string;
142
+ /** Tags that were pushed alongside. @task T963 */
89
143
  tagsPushed: string[];
90
144
  }
91
145
 
92
146
  // release.gates.run
147
+ /** Parameters for `release.gates.run`. @task T963 */
93
148
  export interface ReleaseGatesRunParams {
149
+ /** Specific gate names to run. Omit to run all. @task T963 */
94
150
  gates?: string[];
95
151
  }
152
+ /** Result of `release.gates.run`. @task T963 */
96
153
  export interface ReleaseGatesRunResult {
154
+ /** Total gates evaluated. @task T963 */
97
155
  total: number;
156
+ /** Gates that passed. @task T963 */
98
157
  passed: number;
158
+ /** Gates that failed. @task T963 */
99
159
  failed: number;
160
+ /** Full per-gate report. @task T963 */
100
161
  gates: ReleaseGate[];
162
+ /** True when every gate passed. @task T963 */
101
163
  canRelease: boolean;
102
164
  }
103
165
 
104
166
  // release.rollback
167
+ /** Parameters for `release.rollback`. @task T963 */
105
168
  export interface ReleaseRollbackParams {
169
+ /** Version to roll back. @task T963 */
106
170
  version: string;
171
+ /** Human-readable reason for the rollback. @task T963 */
107
172
  reason: string;
108
173
  }
174
+ /** Result of `release.rollback`. @task T963 */
109
175
  export interface ReleaseRollbackResult {
176
+ /** Version that was rolled back. @task T963 */
110
177
  version: string;
178
+ /** ISO 8601 rollback timestamp. @task T963 */
111
179
  rolledBack: string;
180
+ /** Version that is now current. @task T963 */
112
181
  restoredVersion: string;
182
+ /** Rollback reason. @task T963 */
113
183
  reason: string;
114
184
  }
@@ -29,10 +29,33 @@ export interface SessionOp {
29
29
 
30
30
  // session.status
31
31
  export type SessionStatusParams = Record<string, never>;
32
+ /**
33
+ * Result of `session.status`.
34
+ *
35
+ * @remarks
36
+ * Re-synced to match the envelope returned by `sessionStatus` in
37
+ * `packages/cleo/src/dispatch/engines/session-engine.ts`. The legacy
38
+ * contract's `{current, hasStartedTask, startedTask}` shape predated the
39
+ * current engine and never matched dispatch output — agents reading the
40
+ * contract would never find their data.
41
+ *
42
+ * @task T963 — contract↔impl drift reconciliation (T910 audit)
43
+ */
32
44
  export interface SessionStatusResult {
33
- current: SessionOp | null;
34
- hasStartedTask: boolean;
35
- startedTask?: string;
45
+ /** True when a session is currently active. @task T963 */
46
+ hasActiveSession: boolean;
47
+ /**
48
+ * Active session record (full shape per `Session` contract) or `null`
49
+ * when none active.
50
+ * @task T963
51
+ */
52
+ session?: SessionOp | null;
53
+ /**
54
+ * Current task work state from `meta.focus_state` — tracks the
55
+ * task the orchestrator is focused on within the session.
56
+ * @task T963
57
+ */
58
+ taskWork?: import('../task.js').TaskWorkState | null;
36
59
  }
37
60
 
38
61
  // session.list
@@ -70,9 +70,39 @@ export interface TasksListResult {
70
70
  }
71
71
 
72
72
  // tasks.find
73
+ /**
74
+ * Parameters for `tasks.find`.
75
+ *
76
+ * @remarks
77
+ * Re-synced to match `taskFind(projectRoot, query, limit, options)` in the
78
+ * dispatch layer. Legacy contract exposed only `{query, limit}` and hid
79
+ * the 7 additional filter options agents actually need.
80
+ *
81
+ * @task T963 — contract↔impl drift reconciliation (T910 audit)
82
+ */
73
83
  export interface TasksFindParams {
84
+ /** Free-text search query. @task T963 */
74
85
  query: string;
86
+ /** Max results. @task T963 */
75
87
  limit?: number;
88
+ /** Exact task ID lookup (bypasses search). @task T963 */
89
+ id?: string;
90
+ /** When true, require exact string match instead of fuzzy. @task T963 */
91
+ exact?: boolean;
92
+ /** Filter by status. @task T963 */
93
+ status?: TaskStatus;
94
+ /** When true, include archived tasks in the search. @task T963 */
95
+ includeArchive?: boolean;
96
+ /** Offset into the results (pagination). @task T963 */
97
+ offset?: number;
98
+ /**
99
+ * Comma-separated field projection (e.g. `'id,title,status'`) to shrink
100
+ * the wire payload.
101
+ * @task T963
102
+ */
103
+ fields?: string;
104
+ /** When true, emit verbose per-match diagnostic fields. @task T963 */
105
+ verbose?: boolean;
76
106
  }
77
107
  export type TasksFindResult = MinimalTask[];
78
108
 
@@ -157,33 +187,141 @@ export type TasksNextResult = SuggestedTask[];
157
187
  */
158
188
 
159
189
  // tasks.create
190
+ /**
191
+ * Parameters for `tasks.create` / `tasks.add`.
192
+ *
193
+ * @remarks
194
+ * Re-synced to match `AddTaskOptions` in `packages/core/src/tasks/add.ts`
195
+ * and the dispatch handler in `packages/cleo/src/dispatch/domains/tasks.ts`.
196
+ * The legacy contract was missing 8 fields (`type`, `acceptance`, `phase`,
197
+ * `size`, `notes`, `files`, `dryRun`, `parentSearch`) — agents following
198
+ * the contract would omit them and fail CLEO's anti-hallucination + epic
199
+ * creation enforcement.
200
+ *
201
+ * @task T963 — contract↔impl drift reconciliation (T910 audit)
202
+ */
160
203
  export interface TasksCreateParams {
204
+ /** Task title (required, 1..200 chars). @task T963 */
161
205
  title: string;
206
+ /**
207
+ * Task description (required by anti-hallucination rule T5698 — must
208
+ * differ from `title`). The legacy contract marked this required; kept
209
+ * required here to preserve behavior.
210
+ * @task T963
211
+ */
162
212
  description: string;
213
+ /** Parent task id (`T###`). Omit for root-level tasks (epics only). @task T963 */
163
214
  parent?: string;
215
+ /** Task IDs this task depends on. @task T963 */
164
216
  depends?: string[];
217
+ /** Priority (`critical`|`high`|`medium`|`low`). Defaults to `medium`. @task T963 */
165
218
  priority?: TaskPriority;
219
+ /** Label tags (lowercase alphanumeric + hyphens/periods). @task T963 */
166
220
  labels?: string[];
221
+ /**
222
+ * Task type. When omitted, inferred from parent (epic-child → `task`,
223
+ * task-child → `subtask`, rootless → `task`). @task T963
224
+ */
225
+ type?: 'epic' | 'task' | 'subtask';
226
+ /**
227
+ * Acceptance criteria (pipe-separated strings). Minimum 3 required by
228
+ * enforcement layer; epics require minimum 5.
229
+ * @task T963
230
+ */
231
+ acceptance?: string[];
232
+ /** Phase slug (lowercase alphanumeric + hyphens). @task T963 */
233
+ phase?: string;
234
+ /** Task size (`small`|`medium`|`large`). Defaults to `medium`. @task T963 */
235
+ size?: 'small' | 'medium' | 'large';
236
+ /** Initial note text (timestamped at insertion). @task T963 */
237
+ notes?: string;
238
+ /** File paths associated with the task. @task T963 */
239
+ files?: string[];
240
+ /**
241
+ * When true, validate + preview the task without allocating a task id or
242
+ * writing to the DB. Preview returns an `id: 'T???'` placeholder.
243
+ * @task T963
244
+ */
245
+ dryRun?: boolean;
246
+ /**
247
+ * CLI helper for parent resolution via search term — when set, dispatch
248
+ * layer resolves the search to a parent id before calling core.
249
+ * @task T963
250
+ */
251
+ parentSearch?: string;
167
252
  }
168
253
  export type TasksCreateResult = TaskOp;
169
254
 
170
255
  // tasks.update
256
+ /**
257
+ * Parameters for `tasks.update`.
258
+ *
259
+ * @remarks
260
+ * Re-synced to match `UpdateTaskOptions` in
261
+ * `packages/core/src/tasks/update.ts` and the dispatch handler in
262
+ * `packages/cleo/src/dispatch/domains/tasks.ts`. The legacy contract was
263
+ * missing `acceptance`, `pipelineStage` (T834 / ADR-051 Decision 4),
264
+ * `files`, `blockedBy`, `phase`, `noAutoComplete`, the narrow typing of
265
+ * `type`/`size`, and the `addDepends`/`removeDepends` array mutators.
266
+ *
267
+ * @task T963 — contract↔impl drift reconciliation (T910 audit)
268
+ */
171
269
  export interface TasksUpdateParams {
270
+ /** Task ID to update (required). @task T963 */
172
271
  taskId: string;
272
+ /** Replace title. @task T963 */
173
273
  title?: string;
274
+ /** Replace description. @task T963 */
174
275
  description?: string;
276
+ /** Target status — `done` transitions route through complete flow. @task T963 */
175
277
  status?: TaskStatus;
278
+ /** Replace priority. @task T963 */
176
279
  priority?: TaskPriority;
280
+ /** Append a timestamped note. @task T963 */
177
281
  notes?: string;
178
- parent?: string | null; // Set parent ID, or null/"" to promote to root
282
+ /** Set parent ID, or null/"" to promote to root. @task T963 */
283
+ parent?: string | null;
284
+ /** Replace labels wholesale. @task T963 */
179
285
  labels?: string[];
286
+ /** Additive label insert. @task T963 */
180
287
  addLabels?: string[];
288
+ /** Label removal set. @task T963 */
181
289
  removeLabels?: string[];
290
+ /** Replace dependency list. @task T963 */
182
291
  depends?: string[];
292
+ /** Additive dependency insert. @task T963 */
183
293
  addDepends?: string[];
294
+ /** Dependency removal set. @task T963 */
184
295
  removeDepends?: string[];
185
- type?: string;
186
- size?: string;
296
+ /** Task type (`epic`|`task`|`subtask`). @task T963 */
297
+ type?: 'epic' | 'task' | 'subtask';
298
+ /** Task size (`small`|`medium`|`large`). @task T963 */
299
+ size?: 'small' | 'medium' | 'large';
300
+ /**
301
+ * Replace acceptance criteria. Subject to AC enforcement — min 3 for
302
+ * all tasks, min 5 for epics.
303
+ * @task T963
304
+ */
305
+ acceptance?: string[];
306
+ /** Replace files attached to the task. @task T963 */
307
+ files?: string[];
308
+ /** Phase slug (lowercase alphanumeric + hyphens). @task T963 */
309
+ phase?: string;
310
+ /** Blocking reason (set when status=`blocked`). @task T963 */
311
+ blockedBy?: string;
312
+ /**
313
+ * When true, skip auto-complete cascading when all children complete.
314
+ * @task T963
315
+ */
316
+ noAutoComplete?: boolean;
317
+ /**
318
+ * RCASD-IVTR+C pipeline stage transition target. Forward-only
319
+ * (validated by `validatePipelineTransition`); epic advancements are
320
+ * additionally gated by `validateEpicStageAdvancement`; non-epic tasks
321
+ * are gated by `validateChildStageCeiling` against their ancestor epic.
322
+ * @task T963 / T834 / ADR-051 Decision 4
323
+ */
324
+ pipelineStage?: string;
187
325
  }
188
326
  export type TasksUpdateResult = TaskOp;
189
327
 
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Sentient Loop Tier-2 Contracts — Proposal types.
3
+ *
4
+ * Defines the shared interfaces for the Tier-2 proposal pipeline:
5
+ * ingester candidates, proposed-task metadata, and rate-limit results.
6
+ *
7
+ * These are LEAF types — zero runtime dependencies.
8
+ *
9
+ * @task T1008
10
+ * @see ADR-054 — Sentient Loop Tier-1 (Tier-2 extension)
11
+ */
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Proposal candidate (produced by ingesters)
15
+ // ---------------------------------------------------------------------------
16
+
17
+ /**
18
+ * Source system from which a proposal candidate was derived.
19
+ */
20
+ export type ProposalSource = 'brain' | 'nexus' | 'test';
21
+
22
+ /**
23
+ * A ranked proposal candidate produced by one of the three ingesters.
24
+ *
25
+ * Candidates are merged, deduplicated by fingerprint, scored, and written
26
+ * to tasks.db as rows with `status='proposed'` by the propose tick.
27
+ *
28
+ * Title format is ALWAYS a structured template — no freeform LLM text.
29
+ * This is a prompt-injection defence (T1008 §3.6).
30
+ */
31
+ export interface ProposalCandidate {
32
+ /** Which ingester produced this candidate. */
33
+ source: ProposalSource;
34
+ /**
35
+ * Stable external identifier (brain entry id, nexus node id, file path).
36
+ * Used with `source` to compute the deduplication fingerprint.
37
+ */
38
+ sourceId: string;
39
+ /**
40
+ * Structured title — MUST match pattern `^\\[T2-(BRAIN|NEXUS|TEST)\\]`.
41
+ * No freeform LLM text is permitted here.
42
+ */
43
+ title: string;
44
+ /** Human-readable rationale (template-generated, not LLM-generated). */
45
+ rationale: string;
46
+ /**
47
+ * Relative priority weight in [0, 1].
48
+ * - BRAIN: `(citation_count / 10) * quality_score` capped at 1.0
49
+ * - NEXUS: fixed 0.3 base weight
50
+ * - TEST: fixed 0.5 base weight
51
+ */
52
+ weight: number;
53
+ }
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // Proposed task meta (stored in tasks.metadataJson)
57
+ // ---------------------------------------------------------------------------
58
+
59
+ /**
60
+ * Metadata stored in `tasks.metadataJson` for rows created by the Tier-2
61
+ * proposer (`status='proposed'`).
62
+ *
63
+ * Parsed from JSON at read time; serialized to JSON at write time.
64
+ */
65
+ export interface ProposedTaskMeta {
66
+ /**
67
+ * Identifier tag for the sentient proposer.
68
+ * Always `'sentient-tier2'` for Tier-2 proposals.
69
+ * Used in the transactional rate-limit count query.
70
+ */
71
+ proposedBy: 'sentient-tier2';
72
+ /** Source system that generated this proposal. */
73
+ source: ProposalSource;
74
+ /** External source ID (brain entry id, nexus node id, etc.). */
75
+ sourceId: string;
76
+ /** Computed weight at time of proposal. */
77
+ weight: number;
78
+ /** ISO-8601 timestamp when the proposal was generated. */
79
+ proposedAt: string;
80
+ }
81
+
82
+ // ---------------------------------------------------------------------------
83
+ // Tier-2 stats
84
+ // ---------------------------------------------------------------------------
85
+
86
+ /**
87
+ * Rolling counters for Tier-2 proposal activity, persisted in sentient-state.json.
88
+ */
89
+ export interface Tier2Stats {
90
+ /** Total proposals written to tasks.db as `status='proposed'`. */
91
+ proposalsGenerated: number;
92
+ /**
93
+ * Total proposals transitioned to `pending` by owner accept action.
94
+ */
95
+ proposalsAccepted: number;
96
+ /**
97
+ * Total proposals transitioned to `cancelled` by owner reject action.
98
+ */
99
+ proposalsRejected: number;
100
+ }
@@ -19,6 +19,7 @@ export const TASK_STATUSES = [
19
19
  'done',
20
20
  'cancelled',
21
21
  'archived',
22
+ 'proposed',
22
23
  ] as const;
23
24
 
24
25
  export const SESSION_STATUSES = ['active', 'ended', 'orphaned', 'suspended'] as const;
@@ -151,6 +152,7 @@ export const TASK_STATUS_SYMBOLS_UNICODE: Record<TaskStatus, string> = {
151
152
  done: '\u2713', // ✓ complete
152
153
  cancelled: '\u2717', // ✗ abandoned
153
154
  archived: '\u25a3', // ▣ stored, inactive
155
+ proposed: '\u25c7', // ◇ tier-2 proposal queue (T946)
154
156
  };
155
157
 
156
158
  /**
@@ -163,4 +165,5 @@ export const TASK_STATUS_SYMBOLS_ASCII: Record<TaskStatus, string> = {
163
165
  done: '+',
164
166
  cancelled: '~',
165
167
  archived: '#',
168
+ proposed: '?',
166
169
  };
package/src/task.ts CHANGED
@@ -44,6 +44,39 @@ export type TaskPriority = 'critical' | 'high' | 'medium' | 'low';
44
44
  /** Task type in hierarchy. */
45
45
  export type TaskType = 'epic' | 'task' | 'subtask';
46
46
 
47
+ /**
48
+ * Task role axis — orthogonal to {@link TaskType}, describes the intent of work.
49
+ * Defaults to `'work'` for backward compatibility.
50
+ *
51
+ * @task T944
52
+ */
53
+ export type TaskRole = 'work' | 'research' | 'experiment' | 'bug' | 'spike' | 'release';
54
+
55
+ /**
56
+ * Task scope axis — granularity of work. Orthogonal to {@link TaskType} and
57
+ * {@link TaskRole}. Defaults to `'feature'`.
58
+ *
59
+ * Legacy type → scope mapping on backfill:
60
+ * - `type='epic'` → `scope='project'`
61
+ * - `type='task'` → `scope='feature'`
62
+ * - `type='subtask'` → `scope='unit'`
63
+ *
64
+ * @task T944
65
+ */
66
+ export type TaskScope = 'project' | 'feature' | 'unit';
67
+
68
+ /**
69
+ * Bug severity axis. Only meaningful when `role='bug'`; enforced by a DB-level
70
+ * CHECK constraint (`severity IS NULL OR (severity IN (...) AND role='bug')`).
71
+ *
72
+ * OWNER-WRITE-ONLY: severity is intended to be set through owner-authenticated
73
+ * paths only. This prevents a prompt-injection exploit where a compromised
74
+ * Tier 3 agent could downgrade a P0 bug to P3 to force-ship.
75
+ *
76
+ * @task T944
77
+ */
78
+ export type TaskSeverity = 'P0' | 'P1' | 'P2' | 'P3';
79
+
47
80
  /** Task size (scope, NOT time). */
48
81
  export type TaskSize = 'small' | 'medium' | 'large';
49
82
 
@@ -219,6 +252,27 @@ export interface Task {
219
252
  /** Task type in hierarchy. Inferred from parent context if not specified. @defaultValue undefined */
220
253
  type?: TaskType;
221
254
 
255
+ /**
256
+ * Task role axis — intent of work, orthogonal to {@link type}.
257
+ * Defaults to `'work'` at the DB level. @defaultValue 'work'
258
+ * @task T944
259
+ */
260
+ role?: TaskRole;
261
+
262
+ /**
263
+ * Task scope axis — granularity of work, orthogonal to {@link type} and
264
+ * {@link role}. Defaults to `'feature'` at the DB level. @defaultValue 'feature'
265
+ * @task T944
266
+ */
267
+ scope?: TaskScope;
268
+
269
+ /**
270
+ * Bug severity. Only valid when {@link role} is `'bug'`. OWNER-WRITE-ONLY.
271
+ * @defaultValue undefined
272
+ * @task T944
273
+ */
274
+ severity?: TaskSeverity | null;
275
+
222
276
  /** ID of the parent task. `null` for root-level tasks. @defaultValue undefined */
223
277
  parentId?: string | null;
224
278
 
@@ -356,6 +410,27 @@ export interface TaskCreate {
356
410
  /** Task type. Inferred from parent context if not specified. @defaultValue undefined */
357
411
  type?: TaskType;
358
412
 
413
+ /**
414
+ * Task role — intent of work. Defaults to `'work'` at the DB level.
415
+ * @defaultValue 'work'
416
+ * @task T944
417
+ */
418
+ role?: TaskRole;
419
+
420
+ /**
421
+ * Task scope — granularity of work. Defaults to `'feature'` at the DB level.
422
+ * @defaultValue 'feature'
423
+ * @task T944
424
+ */
425
+ scope?: TaskScope;
426
+
427
+ /**
428
+ * Bug severity (OWNER-WRITE-ONLY). Only valid with `role='bug'`.
429
+ * @defaultValue undefined
430
+ * @task T944
431
+ */
432
+ severity?: TaskSeverity | null;
433
+
359
434
  /** Parent task ID for hierarchy placement. @defaultValue undefined */
360
435
  parentId?: string | null;
361
436