@cleocode/core 2026.4.98 → 2026.4.99

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 (59) hide show
  1. package/dist/gc/daemon-entry.d.ts +15 -0
  2. package/dist/gc/daemon-entry.d.ts.map +1 -0
  3. package/dist/gc/daemon.d.ts +71 -0
  4. package/dist/gc/daemon.d.ts.map +1 -0
  5. package/dist/gc/index.d.ts +14 -0
  6. package/dist/gc/index.d.ts.map +1 -0
  7. package/dist/gc/runner.d.ts +132 -0
  8. package/dist/gc/runner.d.ts.map +1 -0
  9. package/dist/gc/state.d.ts +94 -0
  10. package/dist/gc/state.d.ts.map +1 -0
  11. package/dist/gc/transcript.d.ts +130 -0
  12. package/dist/gc/transcript.d.ts.map +1 -0
  13. package/dist/sentient/daemon-entry.d.ts +11 -0
  14. package/dist/sentient/daemon-entry.d.ts.map +1 -0
  15. package/dist/sentient/daemon.d.ts +160 -0
  16. package/dist/sentient/daemon.d.ts.map +1 -0
  17. package/dist/sentient/index.d.ts +18 -0
  18. package/dist/sentient/index.d.ts.map +1 -0
  19. package/dist/sentient/ingesters/brain-ingester.d.ts +44 -0
  20. package/dist/sentient/ingesters/brain-ingester.d.ts.map +1 -0
  21. package/dist/sentient/ingesters/nexus-ingester.d.ts +45 -0
  22. package/dist/sentient/ingesters/nexus-ingester.d.ts.map +1 -0
  23. package/dist/sentient/ingesters/test-ingester.d.ts +43 -0
  24. package/dist/sentient/ingesters/test-ingester.d.ts.map +1 -0
  25. package/dist/sentient/proposal-rate-limiter.d.ts +93 -0
  26. package/dist/sentient/proposal-rate-limiter.d.ts.map +1 -0
  27. package/dist/sentient/propose-tick.d.ts +105 -0
  28. package/dist/sentient/propose-tick.d.ts.map +1 -0
  29. package/dist/sentient/state.d.ts +143 -0
  30. package/dist/sentient/state.d.ts.map +1 -0
  31. package/dist/sentient/tick.d.ts +193 -0
  32. package/dist/sentient/tick.d.ts.map +1 -0
  33. package/package.json +76 -8
  34. package/src/gc/__tests__/runner.test.ts +367 -0
  35. package/src/gc/__tests__/state.test.ts +169 -0
  36. package/src/gc/__tests__/transcript.test.ts +371 -0
  37. package/src/gc/daemon-entry.ts +26 -0
  38. package/src/gc/daemon.ts +251 -0
  39. package/src/gc/index.ts +14 -0
  40. package/src/gc/runner.ts +378 -0
  41. package/src/gc/state.ts +140 -0
  42. package/src/gc/transcript.ts +380 -0
  43. package/src/sentient/__tests__/brain-ingester.test.ts +154 -0
  44. package/src/sentient/__tests__/daemon.test.ts +472 -0
  45. package/src/sentient/__tests__/dream-tick.test.ts +200 -0
  46. package/src/sentient/__tests__/nexus-ingester.test.ts +138 -0
  47. package/src/sentient/__tests__/proposal-rate-limiter.test.ts +247 -0
  48. package/src/sentient/__tests__/propose-tick.test.ts +296 -0
  49. package/src/sentient/__tests__/test-ingester.test.ts +104 -0
  50. package/src/sentient/daemon-entry.ts +20 -0
  51. package/src/sentient/daemon.ts +471 -0
  52. package/src/sentient/index.ts +18 -0
  53. package/src/sentient/ingesters/brain-ingester.ts +122 -0
  54. package/src/sentient/ingesters/nexus-ingester.ts +171 -0
  55. package/src/sentient/ingesters/test-ingester.ts +205 -0
  56. package/src/sentient/proposal-rate-limiter.ts +172 -0
  57. package/src/sentient/propose-tick.ts +415 -0
  58. package/src/sentient/state.ts +229 -0
  59. package/src/sentient/tick.ts +688 -0
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @cleocode/core/sentient — Tier-1/Tier-2 sentient daemon public API.
3
+ *
4
+ * Provides the autonomous loop logic: tick execution, Tier-2 proposal
5
+ * generation, state management, rate limiting, and ingesters.
6
+ *
7
+ * @see ADR-054 — Sentient Loop Tier-1
8
+ * @package @cleocode/core
9
+ */
10
+ export * from './daemon.js';
11
+ export * from './ingesters/brain-ingester.js';
12
+ export * from './ingesters/nexus-ingester.js';
13
+ export * from './ingesters/test-ingester.js';
14
+ export * from './proposal-rate-limiter.js';
15
+ export * from './propose-tick.js';
16
+ export * from './state.js';
17
+ export * from './tick.js';
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sentient/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,cAAc,aAAa,CAAC;AAC5B,cAAc,+BAA+B,CAAC;AAC9C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * BRAIN Ingester — Tier-2 proposal candidate source.
3
+ *
4
+ * Queries brain.db for recurring-pain observations (citation_count >= 3,
5
+ * last 7 days, quality_score >= 0.5) and returns ranked ProposalCandidate[].
6
+ *
7
+ * Design principles:
8
+ * - NO LLM calls. All data comes from structured SQL queries.
9
+ * - Title is template-generated: `[T2-BRAIN] Recurring issue: {title}`.
10
+ * This is the prompt-injection defence from T1008 §3.6.
11
+ * - Failures are swallowed: returns empty array + logs warning.
12
+ * Brain.db absence must never crash the propose tick.
13
+ *
14
+ * @task T1008
15
+ * @see ADR-054 — Sentient Loop Tier-2
16
+ */
17
+ import type { DatabaseSync } from 'node:sqlite';
18
+ import type { ProposalCandidate } from '@cleocode/contracts';
19
+ /** Maximum candidates returned from a single brain ingester pass. */
20
+ export declare const BRAIN_INGESTER_LIMIT = 10;
21
+ /** Minimum citation count for a brain entry to be considered. */
22
+ export declare const BRAIN_MIN_CITATION_COUNT = 3;
23
+ /** Minimum quality score for a brain entry to be considered. */
24
+ export declare const BRAIN_MIN_QUALITY_SCORE = 0.5;
25
+ /** Lookback window in days for brain observations. */
26
+ export declare const BRAIN_LOOKBACK_DAYS = 7;
27
+ /**
28
+ * Compute candidate weight from citation_count and quality_score.
29
+ * Formula: `(citation_count / 10) * quality_score` capped at 1.0.
30
+ */
31
+ export declare function computeBrainWeight(citationCount: number, qualityScore: number): number;
32
+ /**
33
+ * Run the BRAIN ingester against the provided DatabaseSync handle.
34
+ *
35
+ * Returns at most {@link BRAIN_INGESTER_LIMIT} candidates, sorted by weight
36
+ * descending. Returns an empty array if the database has no matching entries
37
+ * or if any error occurs (errors are swallowed to never crash the tick).
38
+ *
39
+ * @param nativeDb - Open DatabaseSync handle to brain.db. May be null if
40
+ * brain.db has not been initialised; this is treated as zero candidates.
41
+ * @returns Ranked ProposalCandidate array (may be empty).
42
+ */
43
+ export declare function runBrainIngester(nativeDb: DatabaseSync | null): ProposalCandidate[];
44
+ //# sourceMappingURL=brain-ingester.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brain-ingester.d.ts","sourceRoot":"","sources":["../../../src/sentient/ingesters/brain-ingester.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAkB7D,qEAAqE;AACrE,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC,iEAAiE;AACjE,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C,gEAAgE;AAChE,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAE3C,sDAAsD;AACtD,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAMrC;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAEtF;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,GAAG,iBAAiB,EAAE,CA8CnF"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Nexus Ingester — Tier-2 proposal candidate source.
3
+ *
4
+ * Queries nexus.db for structural anomalies and returns ranked
5
+ * ProposalCandidate[]. Two query patterns:
6
+ *
7
+ * A. Orphaned callees: functions that have many callers but make no calls
8
+ * themselves (zero-import, high-in-degree). Suggests dead-end sinks
9
+ * that may be candidates for abstraction or documentation.
10
+ *
11
+ * B. Over-coupled nodes: symbols with total degree (in + out edges) > 20,
12
+ * suggesting high coupling that should be refactored.
13
+ *
14
+ * Design principles:
15
+ * - NO LLM calls. All data comes from structured SQL queries.
16
+ * - Title is template-generated: `[T2-NEXUS] ...`. Prompt-injection defence.
17
+ * - Failures are swallowed: returns empty array + logs warning.
18
+ * Nexus.db absence must never crash the propose tick.
19
+ *
20
+ * @task T1008
21
+ * @see ADR-054 — Sentient Loop Tier-2
22
+ */
23
+ import type { DatabaseSync } from 'node:sqlite';
24
+ import type { ProposalCandidate } from '@cleocode/contracts';
25
+ /** Base weight for all nexus candidates (structural signals, lower priority than brain). */
26
+ export declare const NEXUS_BASE_WEIGHT = 0.3;
27
+ /** Minimum caller count for orphaned-callee detection. */
28
+ export declare const NEXUS_MIN_CALLER_COUNT = 5;
29
+ /** Minimum total degree for over-coupling detection. */
30
+ export declare const NEXUS_MIN_DEGREE = 20;
31
+ /** Maximum results per query. */
32
+ export declare const NEXUS_QUERY_LIMIT = 5;
33
+ /**
34
+ * Run the Nexus ingester against the provided DatabaseSync handle.
35
+ *
36
+ * Returns candidates from both orphaned-callee (Query A) and high-degree
37
+ * (Query B) detection, merged without duplication. Returns an empty array
38
+ * if the database has no matching entries or if any error occurs.
39
+ *
40
+ * @param nativeDb - Open DatabaseSync handle to nexus.db. May be null if
41
+ * nexus.db has not been initialised; this is treated as zero candidates.
42
+ * @returns Ranked ProposalCandidate array (may be empty).
43
+ */
44
+ export declare function runNexusIngester(nativeDb: DatabaseSync | null): ProposalCandidate[];
45
+ //# sourceMappingURL=nexus-ingester.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nexus-ingester.d.ts","sourceRoot":"","sources":["../../../src/sentient/ingesters/nexus-ingester.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAwB7D,4FAA4F;AAC5F,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC,0DAA0D;AAC1D,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,wDAAwD;AACxD,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC,iCAAiC;AACjC,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAmBnC;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,GAAG,iBAAiB,EAAE,CAkFnF"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Test Ingester — Tier-2 proposal candidate source.
3
+ *
4
+ * Reads two data sources:
5
+ *
6
+ * Source A — `.cleo/audit/gates.jsonl`: CLEO evidence gate failure records.
7
+ * Each line is a JSONL record. Lines where `failCount > 0` produce a
8
+ * proposal suggesting a flaky-test guard be added for the failing task.
9
+ *
10
+ * Source B — `.cleo/coverage-summary.json`: vitest coverage JSON summary.
11
+ * Written by `vitest --coverage --reporter json-summary`. Lines where
12
+ * `lines.pct < 80` produce a proposal suggesting coverage improvement.
13
+ * If the file is absent, Source B returns zero candidates (no error).
14
+ *
15
+ * Design principles:
16
+ * - NO LLM calls. All data comes from structured file reads.
17
+ * - Title is template-generated. Prompt-injection defence (T1008 §3.6).
18
+ * - Failures are swallowed: returns empty array + logs warning.
19
+ *
20
+ * @task T1008
21
+ * @see ADR-054 — Sentient Loop Tier-2
22
+ */
23
+ import type { ProposalCandidate } from '@cleocode/contracts';
24
+ /** Relative path from project root to gates.jsonl. */
25
+ export declare const GATES_JSONL_PATH: ".cleo/audit/gates.jsonl";
26
+ /** Relative path from project root to the coverage summary. */
27
+ export declare const COVERAGE_SUMMARY_PATH: ".cleo/coverage-summary.json";
28
+ /** Coverage line percentage below which a proposal is emitted. */
29
+ export declare const MIN_LINE_COVERAGE_PCT = 80;
30
+ /** Base weight for all test ingester candidates. */
31
+ export declare const TEST_BASE_WEIGHT = 0.5;
32
+ /**
33
+ * Run the test ingester against both data sources.
34
+ *
35
+ * Merges Source A (gates.jsonl) and Source B (coverage-summary.json) without
36
+ * duplication. Returns an empty array if both sources yield nothing or if
37
+ * errors occur (errors are swallowed).
38
+ *
39
+ * @param projectRoot - Absolute path to the project root.
40
+ * @returns Combined ProposalCandidate array (may be empty).
41
+ */
42
+ export declare function runTestIngester(projectRoot: string): ProposalCandidate[];
43
+ //# sourceMappingURL=test-ingester.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-ingester.d.ts","sourceRoot":"","sources":["../../../src/sentient/ingesters/test-ingester.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AA6B7D,sDAAsD;AACtD,eAAO,MAAM,gBAAgB,EAAG,yBAAkC,CAAC;AAEnE,+DAA+D;AAC/D,eAAO,MAAM,qBAAqB,EAAG,6BAAsC,CAAC;AAE5E,kEAAkE;AAClE,eAAO,MAAM,qBAAqB,KAAK,CAAC;AAExC,oDAAoD;AACpD,eAAO,MAAM,gBAAgB,MAAM,CAAC;AA6GpC;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAqBxE"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Proposal Rate Limiter — Transactional DB-enforced daily cap.
3
+ *
4
+ * Enforces a maximum of N proposals per day per source tag, using a
5
+ * BEGIN IMMEDIATE transaction with COUNT + conditional INSERT pattern.
6
+ *
7
+ * Design rationale (T1008 §3.2):
8
+ * - In-process counters do not survive daemon restart, and two daemon
9
+ * instances could both allow N/day if enforcement is not in the DB.
10
+ * - The `sentient.lock` advisory lock (daemon.ts) prevents two daemons
11
+ * from running concurrently, but the transactional count check provides
12
+ * belt-and-suspenders protection against TOCTOU races.
13
+ * - SQLite partial unique indexes cannot enforce count > 1, so the
14
+ * BEGIN IMMEDIATE + COUNT + INSERT pattern is used instead.
15
+ *
16
+ * The "day" boundary is determined by SQLite's `date('now')` (UTC).
17
+ * Proposals counted include ALL non-terminal statuses (proposed, pending,
18
+ * active, done) — an accepted proposal still consumes a daily slot.
19
+ *
20
+ * @task T1008
21
+ * @see ADR-054 — Sentient Loop Tier-2
22
+ */
23
+ import type { DatabaseSync, SQLInputValue } from 'node:sqlite';
24
+ /**
25
+ * The meta proposedBy tag written to `tasks.metadata_json` by the Tier-2
26
+ * proposer. This tag is the query key for rate-limit counting.
27
+ */
28
+ export declare const SENTIENT_TIER2_TAG: "sentient-tier2";
29
+ /**
30
+ * Default maximum number of Tier-2 proposals per UTC day.
31
+ * Can be overridden by callers.
32
+ */
33
+ export declare const DEFAULT_DAILY_PROPOSAL_LIMIT = 3;
34
+ /**
35
+ * Count the number of Tier-2 proposals created today (UTC).
36
+ *
37
+ * Counts tasks where:
38
+ * - `labels_json` contains `'sentient-tier2'` (the Tier-2 marker label)
39
+ * - `date(created_at) = date('now')`
40
+ * - `status IN ('proposed', 'pending', 'active', 'done')` — terminal
41
+ * states that were proposed today still count toward the daily cap so
42
+ * that accepted proposals don't free a slot for another proposal.
43
+ *
44
+ * The `LIKE` pattern is intentional: labels_json is a JSON array stored as
45
+ * text, and `'sentient-tier2'` is always a complete JSON string value within
46
+ * that array, making substring matching safe here.
47
+ *
48
+ * @param nativeDb - Open DatabaseSync handle to tasks.db.
49
+ * @returns Number of proposals created today. Returns 0 if DB is null.
50
+ */
51
+ export declare function countTodayProposals(nativeDb: DatabaseSync | null): number;
52
+ /**
53
+ * Check whether the daily rate limit has been reached.
54
+ *
55
+ * @param nativeDb - Open DatabaseSync handle to tasks.db.
56
+ * @param limit - Daily cap (defaults to {@link DEFAULT_DAILY_PROPOSAL_LIMIT}).
57
+ * @returns `true` if the limit is reached or exceeded; `false` if capacity remains.
58
+ */
59
+ export declare function isRateLimitExceeded(nativeDb: DatabaseSync | null, limit?: number): boolean;
60
+ /** Result of a transactional insert attempt. */
61
+ export interface TransactionalInsertResult {
62
+ /** Whether the INSERT was committed. */
63
+ inserted: boolean;
64
+ /**
65
+ * The count read at the start of the transaction. Used for diagnostics
66
+ * and tests — lets callers verify the guard saw the expected count.
67
+ */
68
+ countBeforeInsert: number;
69
+ /**
70
+ * If `inserted = false` and this is set, the limit was the reason.
71
+ */
72
+ reason?: 'rate-limit' | 'busy';
73
+ }
74
+ /**
75
+ * Attempt to insert a pre-built task row inside a BEGIN IMMEDIATE transaction
76
+ * with a count check.
77
+ *
78
+ * Steps:
79
+ * 1. BEGIN IMMEDIATE (exclusive write lock on tasks.db)
80
+ * 2. COUNT proposals created today
81
+ * 3. If count >= limit: ROLLBACK, return `{ inserted: false, reason: 'rate-limit' }`
82
+ * 4. Otherwise: INSERT the row, COMMIT, return `{ inserted: true }`
83
+ *
84
+ * On SQLITE_BUSY: ROLLBACK + return `{ inserted: false, reason: 'busy' }`.
85
+ *
86
+ * @param nativeDb - Open DatabaseSync handle to tasks.db.
87
+ * @param insertSql - Parameterized INSERT SQL string.
88
+ * @param insertParams - Named parameters for the INSERT statement.
89
+ * @param limit - Daily cap.
90
+ * @returns Insert result.
91
+ */
92
+ export declare function transactionalInsertProposal(nativeDb: DatabaseSync, insertSql: string, insertParams: Record<string, SQLInputValue>, limit?: number): TransactionalInsertResult;
93
+ //# sourceMappingURL=proposal-rate-limiter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proposal-rate-limiter.d.ts","sourceRoot":"","sources":["../../src/sentient/proposal-rate-limiter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAM/D;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAG,gBAAyB,CAAC;AAE5D;;;GAGG;AACH,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAY9C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,GAAG,MAAM,CAazE;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,YAAY,GAAG,IAAI,EAC7B,KAAK,SAA+B,GACnC,OAAO,CAET;AAMD,gDAAgD;AAChD,MAAM,WAAW,yBAAyB;IACxC,wCAAwC;IACxC,QAAQ,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,iBAAiB,EAAE,MAAM,CAAC;IAC1B;;OAEG;IACH,MAAM,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC;CAChC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,YAAY,EACtB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,EAC3C,KAAK,SAA+B,GACnC,yBAAyB,CAgC3B"}
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Sentient Loop Propose Tick — Single-pass Tier-2 proposal generator.
3
+ *
4
+ * Runs inside the daemon cron (every 2 hours) or standalone via
5
+ * `cleo sentient propose run`. Orchestrates the three ingesters,
6
+ * deduplicates candidates by fingerprint, applies the DB-enforced
7
+ * rate limit, and writes accepted candidates as tasks with
8
+ * `status='proposed'` and labels including `'sentient-tier2'`.
9
+ *
10
+ * Scoped IN:
11
+ * - Ingest from brain.db, nexus.db, and .cleo/audit/gates.jsonl
12
+ * - Transactional rate-limit check (BEGIN IMMEDIATE + COUNT + INSERT)
13
+ * - Kill-switch re-check at each checkpoint (Round 2 audit §9)
14
+ * - tier2Enabled guard (default false — owner opt-in)
15
+ *
16
+ * Scoped OUT:
17
+ * - LLM calls (NONE — all proposal titles are structured templates)
18
+ * - Tier-3 sandbox/merge (blocked on T992+T993+T995)
19
+ *
20
+ * Title format enforcement:
21
+ * All proposal titles MUST match `/^\[T2-(BRAIN|NEXUS|TEST)\]/`.
22
+ * This is the prompt-injection defence from T1008 §3.6 — no freeform
23
+ * LLM text can enter the task title column from the Tier-2 proposer.
24
+ *
25
+ * @task T1008
26
+ * @see ADR-054 — Sentient Loop Tier-2
27
+ */
28
+ /**
29
+ * Regex that ALL proposal titles MUST match.
30
+ * Enforces structured-template-only output (no freeform LLM text).
31
+ */
32
+ export declare const PROPOSAL_TITLE_PATTERN: RegExp;
33
+ /**
34
+ * Label applied to every Tier-2 proposal task.
35
+ * Used by the rate limiter to identify proposals.
36
+ */
37
+ export declare const TIER2_LABEL = "sentient-tier2";
38
+ /** Discriminant for the propose-tick outcome. */
39
+ export type ProposalTickOutcomeKind = 'killed' | 'disabled' | 'rate-limited' | 'no-candidates' | 'wrote' | 'error';
40
+ /** Structured outcome of a single propose-tick pass. */
41
+ export interface ProposeTickOutcome {
42
+ /** Discriminant describing how the tick ended. */
43
+ kind: ProposalTickOutcomeKind;
44
+ /** Number of proposals written in this pass. */
45
+ written: number;
46
+ /** Current daily proposal count at the end of the pass. */
47
+ count: number;
48
+ /** Human-readable detail (one line). */
49
+ detail: string;
50
+ }
51
+ /** Options for {@link runProposeTick}. */
52
+ export interface ProposeTickOptions {
53
+ /** Absolute path to the project root (contains `.cleo/`). */
54
+ projectRoot: string;
55
+ /** Absolute path to sentient-state.json. */
56
+ statePath: string;
57
+ /**
58
+ * Override for the brain DB handle. Injected by tests to avoid
59
+ * opening a real brain.db. When omitted the real getBrainNativeDb() is used.
60
+ */
61
+ brainDb?: import('node:sqlite').DatabaseSync | null;
62
+ /**
63
+ * Override for the nexus DB handle. Injected by tests.
64
+ * When omitted the real getNexusNativeDb() is used.
65
+ */
66
+ nexusDb?: import('node:sqlite').DatabaseSync | null;
67
+ /**
68
+ * Override for the tasks DB handle (used by rate limiter + INSERT).
69
+ * Injected by tests. When omitted the real getNativeTasksDb() is used.
70
+ */
71
+ tasksDb?: import('node:sqlite').DatabaseSync | null;
72
+ /**
73
+ * Override the task ID allocator. Injected by tests.
74
+ * When omitted the real allocateNextTaskId() is used.
75
+ */
76
+ allocateTaskId?: () => Promise<string>;
77
+ }
78
+ /**
79
+ * Run a single Tier-2 propose pass.
80
+ *
81
+ * Steps:
82
+ * 1. Check killSwitch → abort if true
83
+ * 2. Check tier2Enabled → abort if false
84
+ * 3. Run all three ingesters in parallel
85
+ * 4. Check killSwitch again (post-ingest checkpoint)
86
+ * 5. Merge + deduplicate candidates by fingerprint
87
+ * 6. Validate title format (must match PROPOSAL_TITLE_PATTERN)
88
+ * 7. Score + take top-N candidates (N = limit - countTodayProposals)
89
+ * 8. Check killSwitch again (pre-write checkpoint)
90
+ * 9. For each candidate: transactional INSERT into tasks.db
91
+ * 10. Update tier2Stats in state
92
+ *
93
+ * @param options - Propose tick options (see {@link ProposeTickOptions})
94
+ * @returns Structured outcome describing how the pass ended.
95
+ */
96
+ export declare function runProposeTick(options: ProposeTickOptions): Promise<ProposeTickOutcome>;
97
+ /**
98
+ * Safe wrapper for {@link runProposeTick} — swallows unexpected exceptions.
99
+ * Used by the daemon cron handler.
100
+ *
101
+ * @param options - Propose tick options
102
+ * @returns The propose tick outcome, or an error outcome if the tick threw.
103
+ */
104
+ export declare function safeRunProposeTick(options: ProposeTickOptions): Promise<ProposeTickOutcome>;
105
+ //# sourceMappingURL=propose-tick.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"propose-tick.d.ts","sourceRoot":"","sources":["../../src/sentient/propose-tick.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAiBH;;;GAGG;AACH,eAAO,MAAM,sBAAsB,QAA+B,CAAC;AAEnE;;;GAGG;AACH,eAAO,MAAM,WAAW,mBAAmB,CAAC;AAM5C,iDAAiD;AACjD,MAAM,MAAM,uBAAuB,GAC/B,QAAQ,GACR,UAAU,GACV,cAAc,GACd,eAAe,GACf,OAAO,GACP,OAAO,CAAC;AAEZ,wDAAwD;AACxD,MAAM,WAAW,kBAAkB;IACjC,kDAAkD;IAClD,IAAI,EAAE,uBAAuB,CAAC;IAC9B,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;CAChB;AAMD,0CAA0C;AAC1C,MAAM,WAAW,kBAAkB;IACjC,6DAA6D;IAC7D,WAAW,EAAE,MAAM,CAAC;IACpB,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,aAAa,EAAE,YAAY,GAAG,IAAI,CAAC;IACpD;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,aAAa,EAAE,YAAY,GAAG,IAAI,CAAC;IACpD;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,aAAa,EAAE,YAAY,GAAG,IAAI,CAAC;IACpD;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;CACxC;AA0BD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA+O7F;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAYjG"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Sentient Loop State — Persistent state for the Tier-1 autonomous daemon.
3
+ *
4
+ * Stored in `.cleo/sentient-state.json` (plain JSON, not SQLite) to avoid
5
+ * SQLite WAL conflicts between the long-running daemon process and the
6
+ * main CLEO CLI process. Human-readable for debugging.
7
+ *
8
+ * The file is gitignored (see .gitignore §.cleo/ section) and survives
9
+ * restarts. Only `killSwitch`, `pid`, and `stats` fields are load-bearing
10
+ * across process boundaries.
11
+ *
12
+ * @see ADR-054 — Sentient Loop Tier-1 (autonomous task execution)
13
+ * @task T946
14
+ */
15
+ import type { Tier2Stats } from '@cleocode/contracts';
16
+ /** Schema version for sentient-state.json. Bump on breaking field changes. */
17
+ export declare const SENTIENT_STATE_SCHEMA_VERSION: "1.0";
18
+ /**
19
+ * Per-task failure/backoff tracking for stuck detection.
20
+ * Keyed by task id in {@link SentientState.stuckTasks}.
21
+ */
22
+ export interface StuckTaskRecord {
23
+ /** Number of consecutive failed spawn attempts for this task. */
24
+ attempts: number;
25
+ /** ISO-8601 timestamp of the most recent failure. */
26
+ lastFailureAt: string;
27
+ /** Unix epoch ms when the next retry becomes eligible. */
28
+ nextRetryAt: number;
29
+ /** Last captured failure reason (truncated to 500 chars). */
30
+ lastReason: string;
31
+ }
32
+ /**
33
+ * Rolling counters persisted across daemon restarts.
34
+ */
35
+ export interface SentientStats {
36
+ /** Total tasks picked by the loop since creation. */
37
+ tasksPicked: number;
38
+ /** Total tasks that completed successfully. */
39
+ tasksCompleted: number;
40
+ /** Total tasks whose spawn exited non-zero. */
41
+ tasksFailed: number;
42
+ /** Total ticks executed (including no-op ticks). */
43
+ ticksExecuted: number;
44
+ /** Total ticks aborted early because kill switch was active. */
45
+ ticksKilled: number;
46
+ }
47
+ /**
48
+ * Persistent sentient daemon state.
49
+ *
50
+ * Design principles:
51
+ * - `killSwitch` is the single load-bearing kill signal — the daemon re-checks
52
+ * it between every step of a tick, not just at tick start (Round 2 audit).
53
+ * - `stuckTasks` keys are task ids; values encode backoff + failure counts.
54
+ * - `stuckTimestamps` is a rolling 1-hour window used for the self-pause rule
55
+ * (5 stucks in 1 hour → killSwitch=true).
56
+ * - `stats` fields are monotonic counters that only ever increase.
57
+ */
58
+ export interface SentientState {
59
+ /** JSON schema version for forward-compatibility checks. */
60
+ schemaVersion: typeof SENTIENT_STATE_SCHEMA_VERSION;
61
+ /** PID of the currently running daemon process. null = daemon not running. */
62
+ pid: number | null;
63
+ /** ISO-8601 timestamp when the daemon was last started. */
64
+ startedAt: string | null;
65
+ /** ISO-8601 timestamp of the last completed tick (any outcome). */
66
+ lastTickAt: string | null;
67
+ /**
68
+ * Kill-switch flag. When true, the daemon re-checks at every step of a tick
69
+ * and exits cleanly without picking or spawning a task.
70
+ */
71
+ killSwitch: boolean;
72
+ /** Reason supplied when killSwitch was last set (diagnostic only). */
73
+ killSwitchReason: string | null;
74
+ /** Rolling counters; see {@link SentientStats}. */
75
+ stats: SentientStats;
76
+ /** Per-task backoff + failure metadata for retry/stuck detection. */
77
+ stuckTasks: Record<string, StuckTaskRecord>;
78
+ /**
79
+ * Unix-epoch-ms timestamps of `stuck` events within the last hour.
80
+ * When length ≥ 5 the daemon self-pauses (killSwitch=true).
81
+ */
82
+ stuckTimestamps: number[];
83
+ /**
84
+ * Currently-active task id (set while a spawn is in-flight, cleared afterward).
85
+ * Enables `status` to show the in-progress task during a long-running tick.
86
+ */
87
+ activeTaskId: string | null;
88
+ /**
89
+ * Tier-2 proposal queue enabled flag.
90
+ *
91
+ * Default: `false` — Tier 2 is OFF by default to prevent surprise proposal
92
+ * floods on first daemon start. Owner enables via `cleo sentient propose enable`
93
+ * (patches this flag). See ADR-054 §Tier-2.
94
+ *
95
+ * @task T1008
96
+ */
97
+ tier2Enabled: boolean;
98
+ /**
99
+ * Rolling counters for Tier-2 proposal activity.
100
+ *
101
+ * @task T1008
102
+ */
103
+ tier2Stats: Tier2Stats;
104
+ }
105
+ /** Default (empty) sentient state for fresh initialisation. */
106
+ export declare const DEFAULT_SENTIENT_STATE: SentientState;
107
+ /**
108
+ * Read the sentient state from disk.
109
+ *
110
+ * Returns the default state if the file does not exist or is malformed.
111
+ * Never throws — absence is not an error.
112
+ *
113
+ * @param statePath - Absolute path to sentient-state.json
114
+ */
115
+ export declare function readSentientState(statePath: string): Promise<SentientState>;
116
+ /**
117
+ * Write the sentient state to disk atomically via tmp-then-rename.
118
+ *
119
+ * Atomic write prevents partial reads if the daemon crashes mid-write.
120
+ *
121
+ * @param statePath - Absolute path to sentient-state.json
122
+ * @param state - State to persist
123
+ */
124
+ export declare function writeSentientState(statePath: string, state: SentientState): Promise<void>;
125
+ /**
126
+ * Patch a subset of fields in the sentient state file.
127
+ *
128
+ * Reads current state, merges patch, writes back. Nested `stats` merges
129
+ * with existing stats (never clobbered wholesale).
130
+ *
131
+ * @param statePath - Absolute path to sentient-state.json
132
+ * @param patch - Partial state to merge over the existing state
133
+ * @returns The merged state that was written to disk.
134
+ */
135
+ export declare function patchSentientState(statePath: string, patch: Partial<SentientState>): Promise<SentientState>;
136
+ /**
137
+ * Increment stats counters atomically.
138
+ *
139
+ * @param statePath - Absolute path to sentient-state.json
140
+ * @param delta - Partial stats to add to current counters
141
+ */
142
+ export declare function incrementStats(statePath: string, delta: Partial<SentientStats>): Promise<SentientState>;
143
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/sentient/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtD,8EAA8E;AAC9E,eAAO,MAAM,6BAA6B,EAAG,KAAc,CAAC;AAE5D;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,aAAa,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,WAAW,EAAE,MAAM,CAAC;IACpB,6DAA6D;IAC7D,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,qDAAqD;IACrD,WAAW,EAAE,MAAM,CAAC;IACpB,+CAA+C;IAC/C,cAAc,EAAE,MAAM,CAAC;IACvB,+CAA+C;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,aAAa,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,aAAa;IAC5B,4DAA4D;IAC5D,aAAa,EAAE,OAAO,6BAA6B,CAAC;IACpD,8EAA8E;IAC9E,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,2DAA2D;IAC3D,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,mEAAmE;IACnE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;OAGG;IACH,UAAU,EAAE,OAAO,CAAC;IACpB,sEAAsE;IACtE,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,mDAAmD;IACnD,KAAK,EAAE,aAAa,CAAC;IACrB,qEAAqE;IACrE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC5C;;;OAGG;IACH,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B;;;OAGG;IACH,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B;;;;;;;;OAQG;IACH,YAAY,EAAE,OAAO,CAAC;IACtB;;;;OAIG;IACH,UAAU,EAAE,UAAU,CAAC;CACxB;AAED,+DAA+D;AAC/D,eAAO,MAAM,sBAAsB,EAAE,aAuBpC,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAgBjF;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAS/F;AAED;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,OAAO,CAAC,aAAa,CAAC,GAC5B,OAAO,CAAC,aAAa,CAAC,CASxB;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,OAAO,CAAC,aAAa,CAAC,GAC5B,OAAO,CAAC,aAAa,CAAC,CAYxB"}