prjct-cli 1.18.0 → 1.20.0

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.
@@ -4277,6 +4277,30 @@ var init_database = __esm({
4277
4277
  );
4278
4278
  `);
4279
4279
  }, "up")
4280
+ },
4281
+ {
4282
+ version: 2,
4283
+ name: "archives-table",
4284
+ up: /* @__PURE__ */ __name((db) => {
4285
+ db.run(`
4286
+ -- =======================================================================
4287
+ -- Archives: Stale data moved out of active storage (PRJ-267)
4288
+ -- =======================================================================
4289
+ CREATE TABLE archives (
4290
+ id TEXT PRIMARY KEY,
4291
+ entity_type TEXT NOT NULL,
4292
+ entity_id TEXT NOT NULL,
4293
+ entity_data TEXT NOT NULL,
4294
+ summary TEXT,
4295
+ archived_at TEXT NOT NULL,
4296
+ reason TEXT NOT NULL
4297
+ );
4298
+
4299
+ CREATE INDEX idx_archives_entity_type ON archives(entity_type);
4300
+ CREATE INDEX idx_archives_archived_at ON archives(archived_at);
4301
+ CREATE INDEX idx_archives_entity_id ON archives(entity_id);
4302
+ `);
4303
+ }, "up")
4280
4304
  }
4281
4305
  ];
4282
4306
  PrjctDatabase = class {
@@ -9716,7 +9740,7 @@ var init_ideas = __esm({
9716
9740
  "core/schemas/ideas.ts"() {
9717
9741
  "use strict";
9718
9742
  IdeaPrioritySchema = z6.enum(["low", "medium", "high"]);
9719
- IdeaStatusSchema = z6.enum(["pending", "converted", "completed", "archived"]);
9743
+ IdeaStatusSchema = z6.enum(["pending", "converted", "completed", "archived", "dormant"]);
9720
9744
  ImpactLevelSchema = z6.enum(["high", "medium", "low"]);
9721
9745
  ImpactEffortSchema = z6.object({
9722
9746
  impact: ImpactLevelSchema,
@@ -12726,6 +12750,147 @@ var init_analysis_storage = __esm({
12726
12750
  }
12727
12751
  });
12728
12752
 
12753
+ // core/storage/archive-storage.ts
12754
+ var ARCHIVE_POLICIES, ArchiveStorage, archiveStorage;
12755
+ var init_archive_storage = __esm({
12756
+ "core/storage/archive-storage.ts"() {
12757
+ "use strict";
12758
+ init_schemas2();
12759
+ init_date_helper();
12760
+ init_database();
12761
+ ARCHIVE_POLICIES = {
12762
+ SHIPPED_RETENTION_DAYS: 90,
12763
+ IDEA_DORMANT_DAYS: 180,
12764
+ QUEUE_COMPLETED_DAYS: 7,
12765
+ PAUSED_TASK_DAYS: 30,
12766
+ MEMORY_MAX_ENTRIES: 500
12767
+ };
12768
+ ArchiveStorage = class {
12769
+ static {
12770
+ __name(this, "ArchiveStorage");
12771
+ }
12772
+ /**
12773
+ * Archive a single item
12774
+ */
12775
+ archive(projectId, item) {
12776
+ const id = generateUUID();
12777
+ const now = getTimestamp();
12778
+ prjctDb.run(
12779
+ projectId,
12780
+ "INSERT INTO archives (id, entity_type, entity_id, entity_data, summary, archived_at, reason) VALUES (?, ?, ?, ?, ?, ?, ?)",
12781
+ id,
12782
+ item.entityType,
12783
+ item.entityId,
12784
+ JSON.stringify(item.entityData),
12785
+ item.summary ?? null,
12786
+ now,
12787
+ item.reason
12788
+ );
12789
+ return id;
12790
+ }
12791
+ /**
12792
+ * Archive multiple items in a transaction
12793
+ */
12794
+ archiveMany(projectId, items) {
12795
+ if (items.length === 0) return 0;
12796
+ const now = getTimestamp();
12797
+ prjctDb.transaction(projectId, (db) => {
12798
+ const stmt = db.prepare(
12799
+ "INSERT INTO archives (id, entity_type, entity_id, entity_data, summary, archived_at, reason) VALUES (?, ?, ?, ?, ?, ?, ?)"
12800
+ );
12801
+ for (const item of items) {
12802
+ stmt.run(
12803
+ generateUUID(),
12804
+ item.entityType,
12805
+ item.entityId,
12806
+ JSON.stringify(item.entityData),
12807
+ item.summary ?? null,
12808
+ now,
12809
+ item.reason
12810
+ );
12811
+ }
12812
+ });
12813
+ return items.length;
12814
+ }
12815
+ /**
12816
+ * Query archived items by type
12817
+ */
12818
+ getArchived(projectId, entityType, limit = 50) {
12819
+ if (entityType) {
12820
+ return prjctDb.query(
12821
+ projectId,
12822
+ "SELECT * FROM archives WHERE entity_type = ? ORDER BY archived_at DESC LIMIT ?",
12823
+ entityType,
12824
+ limit
12825
+ );
12826
+ }
12827
+ return prjctDb.query(
12828
+ projectId,
12829
+ "SELECT * FROM archives ORDER BY archived_at DESC LIMIT ?",
12830
+ limit
12831
+ );
12832
+ }
12833
+ /**
12834
+ * Get count of archived items by type
12835
+ */
12836
+ getStats(projectId) {
12837
+ const rows = prjctDb.query(
12838
+ projectId,
12839
+ "SELECT entity_type, COUNT(*) as count FROM archives GROUP BY entity_type"
12840
+ );
12841
+ const stats = {
12842
+ shipped: 0,
12843
+ idea: 0,
12844
+ queue_task: 0,
12845
+ paused_task: 0,
12846
+ memory_entry: 0,
12847
+ total: 0
12848
+ };
12849
+ for (const row of rows) {
12850
+ const type = row.entity_type;
12851
+ if (type in stats) {
12852
+ stats[type] = row.count;
12853
+ }
12854
+ stats.total += row.count;
12855
+ }
12856
+ return stats;
12857
+ }
12858
+ /**
12859
+ * Restore an item from archive (removes from archive table)
12860
+ * Returns the entity data for the caller to re-insert into active storage.
12861
+ */
12862
+ restore(projectId, archiveId) {
12863
+ const record = prjctDb.get(
12864
+ projectId,
12865
+ "SELECT * FROM archives WHERE id = ?",
12866
+ archiveId
12867
+ );
12868
+ if (!record) return null;
12869
+ prjctDb.run(projectId, "DELETE FROM archives WHERE id = ?", archiveId);
12870
+ return JSON.parse(record.entity_data);
12871
+ }
12872
+ /**
12873
+ * Permanently delete archives older than N days
12874
+ */
12875
+ pruneOldArchives(projectId, olderThanDays) {
12876
+ const threshold = new Date(Date.now() - olderThanDays * 24 * 60 * 60 * 1e3).toISOString();
12877
+ const before = this.getTotalCount(projectId);
12878
+ prjctDb.run(projectId, "DELETE FROM archives WHERE archived_at < ?", threshold);
12879
+ const after = this.getTotalCount(projectId);
12880
+ return before - after;
12881
+ }
12882
+ /**
12883
+ * Get total count of archived items
12884
+ */
12885
+ getTotalCount(projectId) {
12886
+ const row = prjctDb.get(projectId, "SELECT COUNT(*) as count FROM archives");
12887
+ return row?.count ?? 0;
12888
+ }
12889
+ };
12890
+ archiveStorage = new ArchiveStorage();
12891
+ }
12892
+ });
12893
+
12729
12894
  // core/storage/ideas-storage.ts
12730
12895
  var IdeasStorage, ideasStorage;
12731
12896
  var init_ideas_storage = __esm({
@@ -12734,6 +12899,7 @@ var init_ideas_storage = __esm({
12734
12899
  init_schemas2();
12735
12900
  init_ideas();
12736
12901
  init_date_helper();
12902
+ init_archive_storage();
12737
12903
  init_storage_manager();
12738
12904
  IdeasStorage = class extends StorageManager {
12739
12905
  static {
@@ -12762,6 +12928,7 @@ var init_ideas_storage = __esm({
12762
12928
  const pending = data.ideas.filter((i) => i.status === "pending");
12763
12929
  const converted = data.ideas.filter((i) => i.status === "converted");
12764
12930
  const archived = data.ideas.filter((i) => i.status === "archived");
12931
+ const dormant = data.ideas.filter((i) => i.status === "dormant");
12765
12932
  lines.push("## Brain Dump");
12766
12933
  if (pending.length > 0) {
12767
12934
  pending.forEach((idea) => {
@@ -12791,6 +12958,10 @@ var init_ideas_storage = __esm({
12791
12958
  });
12792
12959
  lines.push("");
12793
12960
  }
12961
+ if (dormant.length > 0) {
12962
+ lines.push(`_${dormant.length} dormant idea(s) excluded from context_`);
12963
+ lines.push("");
12964
+ }
12794
12965
  return lines.join("\n");
12795
12966
  }
12796
12967
  // =========== Domain Methods ===========
@@ -12926,6 +13097,40 @@ var init_ideas_storage = __esm({
12926
13097
  }));
12927
13098
  return { removed };
12928
13099
  }
13100
+ /**
13101
+ * Mark pending ideas older than retention period as dormant (PRJ-267).
13102
+ * Dormant ideas are excluded from LLM context but remain queryable.
13103
+ * Returns count of newly dormant ideas.
13104
+ */
13105
+ async markDormantIdeas(projectId) {
13106
+ const data = await this.read(projectId);
13107
+ const threshold = getDaysAgo(ARCHIVE_POLICIES.IDEA_DORMANT_DAYS);
13108
+ const stalePending = data.ideas.filter(
13109
+ (i) => i.status === "pending" && new Date(i.addedAt) < threshold
13110
+ );
13111
+ if (stalePending.length === 0) return 0;
13112
+ archiveStorage.archiveMany(
13113
+ projectId,
13114
+ stalePending.map((idea) => ({
13115
+ entityType: "idea",
13116
+ entityId: idea.id,
13117
+ entityData: idea,
13118
+ summary: idea.text,
13119
+ reason: "dormant"
13120
+ }))
13121
+ );
13122
+ const staleIds = new Set(stalePending.map((i) => i.id));
13123
+ await this.update(projectId, (d) => ({
13124
+ ideas: d.ideas.map(
13125
+ (i) => staleIds.has(i.id) ? { ...i, status: "dormant" } : i
13126
+ ),
13127
+ lastUpdated: getTimestamp()
13128
+ }));
13129
+ await this.publishEvent(projectId, "ideas.dormant", {
13130
+ count: stalePending.length
13131
+ });
13132
+ return stalePending.length;
13133
+ }
12929
13134
  };
12930
13135
  ideasStorage = new IdeasStorage();
12931
13136
  }
@@ -13764,6 +13969,7 @@ var init_queue_storage = __esm({
13764
13969
  init_schemas2();
13765
13970
  init_state();
13766
13971
  init_date_helper();
13972
+ init_archive_storage();
13767
13973
  init_storage_manager();
13768
13974
  QueueStorage = class extends StorageManager {
13769
13975
  static {
@@ -13969,6 +14175,38 @@ var init_queue_storage = __esm({
13969
14175
  }));
13970
14176
  return completedCount;
13971
14177
  }
14178
+ /**
14179
+ * Remove completed tasks older than retention period (PRJ-267).
14180
+ * Archives them to SQLite before removal.
14181
+ * Returns count of removed tasks.
14182
+ */
14183
+ async removeStaleCompleted(projectId) {
14184
+ const queue = await this.read(projectId);
14185
+ const threshold = getDaysAgo(ARCHIVE_POLICIES.QUEUE_COMPLETED_DAYS);
14186
+ const stale = queue.tasks.filter(
14187
+ (t) => t.completed && t.completedAt && new Date(t.completedAt) < threshold
14188
+ );
14189
+ if (stale.length === 0) return 0;
14190
+ archiveStorage.archiveMany(
14191
+ projectId,
14192
+ stale.map((t) => ({
14193
+ entityType: "queue_task",
14194
+ entityId: t.id,
14195
+ entityData: t,
14196
+ summary: t.description,
14197
+ reason: "age"
14198
+ }))
14199
+ );
14200
+ const staleIds = new Set(stale.map((t) => t.id));
14201
+ await this.update(projectId, (q) => ({
14202
+ tasks: q.tasks.filter((t) => !staleIds.has(t.id)),
14203
+ lastUpdated: getTimestamp()
14204
+ }));
14205
+ await this.publishEvent(projectId, "queue.stale_removed", {
14206
+ count: stale.length
14207
+ });
14208
+ return stale.length;
14209
+ }
13972
14210
  /**
13973
14211
  * Sort tasks by priority and section
13974
14212
  */
@@ -14005,6 +14243,7 @@ var init_shipped_storage = __esm({
14005
14243
  init_schemas2();
14006
14244
  init_shipped();
14007
14245
  init_date_helper();
14246
+ init_archive_storage();
14008
14247
  init_storage_manager();
14009
14248
  ShippedStorage = class extends StorageManager {
14010
14249
  static {
@@ -14154,6 +14393,39 @@ var init_shipped_storage = __esm({
14154
14393
  period
14155
14394
  };
14156
14395
  }
14396
+ /**
14397
+ * Archive shipped features older than retention period (PRJ-267).
14398
+ * Moves old items to archive table, keeps 1-line summary in active storage.
14399
+ * Returns count of archived items.
14400
+ */
14401
+ async archiveOldShipped(projectId) {
14402
+ const data = await this.read(projectId);
14403
+ const threshold = getDaysAgo(ARCHIVE_POLICIES.SHIPPED_RETENTION_DAYS);
14404
+ const stale = data.shipped.filter((s) => new Date(s.shippedAt) < threshold);
14405
+ if (stale.length === 0) return 0;
14406
+ archiveStorage.archiveMany(
14407
+ projectId,
14408
+ stale.map((s) => ({
14409
+ entityType: "shipped",
14410
+ entityId: s.id,
14411
+ entityData: s,
14412
+ summary: `${s.name} v${s.version}`,
14413
+ reason: "age"
14414
+ }))
14415
+ );
14416
+ const freshIds = new Set(
14417
+ data.shipped.filter((s) => new Date(s.shippedAt) >= threshold).map((s) => s.id)
14418
+ );
14419
+ await this.update(projectId, (d) => ({
14420
+ shipped: d.shipped.filter((s) => freshIds.has(s.id)),
14421
+ lastUpdated: getTimestamp()
14422
+ }));
14423
+ await this.publishEvent(projectId, "shipped.archived", {
14424
+ count: stale.length,
14425
+ oldestShippedAt: stale[stale.length - 1]?.shippedAt
14426
+ });
14427
+ return stale.length;
14428
+ }
14157
14429
  };
14158
14430
  shippedStorage = new ShippedStorage();
14159
14431
  }
@@ -14557,6 +14829,7 @@ var init_state_storage = __esm({
14557
14829
  init_date_helper();
14558
14830
  init_markdown_builder();
14559
14831
  init_state_machine();
14832
+ init_archive_storage();
14560
14833
  init_storage_manager();
14561
14834
  StateStorage = class extends StorageManager {
14562
14835
  static {
@@ -14809,8 +15082,9 @@ var init_state_storage = __esm({
14809
15082
  return pausedTasks.filter((t) => new Date(t.pausedAt).getTime() < threshold);
14810
15083
  }
14811
15084
  /**
14812
- * Archive stale paused tasks (remove from pausedTasks)
14813
- * Returns archived tasks
15085
+ * Archive stale paused tasks (PRJ-267).
15086
+ * Persists to archive table before removing from active storage.
15087
+ * Returns archived tasks.
14814
15088
  */
14815
15089
  async archiveStalePausedTasks(projectId) {
14816
15090
  const state = await this.read(projectId);
@@ -14819,6 +15093,16 @@ var init_state_storage = __esm({
14819
15093
  const stale = pausedTasks.filter((t) => new Date(t.pausedAt).getTime() < threshold);
14820
15094
  const fresh = pausedTasks.filter((t) => new Date(t.pausedAt).getTime() >= threshold);
14821
15095
  if (stale.length === 0) return [];
15096
+ archiveStorage.archiveMany(
15097
+ projectId,
15098
+ stale.map((task) => ({
15099
+ entityType: "paused_task",
15100
+ entityId: task.id,
15101
+ entityData: task,
15102
+ summary: task.description,
15103
+ reason: "staleness"
15104
+ }))
15105
+ );
14822
15106
  await this.update(projectId, (s) => ({
14823
15107
  ...s,
14824
15108
  pausedTasks: fresh,
@@ -15434,6 +15718,7 @@ var init_storage2 = __esm({
15434
15718
  "core/storage/index.ts"() {
15435
15719
  "use strict";
15436
15720
  init_analysis_storage();
15721
+ init_archive_storage();
15437
15722
  init_database();
15438
15723
  init_ideas_storage();
15439
15724
  init_index_storage();
@@ -18572,6 +18857,211 @@ When fragmenting tasks:
18572
18857
  }
18573
18858
  });
18574
18859
 
18860
+ // core/utils/retry.ts
18861
+ function isTransientError(error) {
18862
+ if (!error || typeof error !== "object") {
18863
+ return false;
18864
+ }
18865
+ const err = error;
18866
+ if (err.code && TRANSIENT_ERROR_CODES.has(err.code)) {
18867
+ return true;
18868
+ }
18869
+ if (err.code && PERMANENT_ERROR_CODES.has(err.code)) {
18870
+ return false;
18871
+ }
18872
+ if (err.message) {
18873
+ const msg = err.message.toLowerCase();
18874
+ if (msg.includes("timeout") || msg.includes("timed out")) {
18875
+ return true;
18876
+ }
18877
+ }
18878
+ return false;
18879
+ }
18880
+ function isPermanentError(error) {
18881
+ if (!error || typeof error !== "object") {
18882
+ return false;
18883
+ }
18884
+ const err = error;
18885
+ return !!(err.code && PERMANENT_ERROR_CODES.has(err.code));
18886
+ }
18887
+ function isCircuitOpen(operationId, threshold, timeoutMs) {
18888
+ const state = circuitStates.get(operationId);
18889
+ if (!state) {
18890
+ return false;
18891
+ }
18892
+ if (state.consecutiveFailures >= threshold && state.openedAt) {
18893
+ const elapsed = Date.now() - state.openedAt;
18894
+ if (elapsed >= timeoutMs) {
18895
+ circuitStates.delete(operationId);
18896
+ return false;
18897
+ }
18898
+ return true;
18899
+ }
18900
+ return false;
18901
+ }
18902
+ function recordFailure(operationId, threshold) {
18903
+ const state = circuitStates.get(operationId) || {
18904
+ consecutiveFailures: 0,
18905
+ openedAt: null
18906
+ };
18907
+ state.consecutiveFailures++;
18908
+ if (state.consecutiveFailures >= threshold && !state.openedAt) {
18909
+ state.openedAt = Date.now();
18910
+ }
18911
+ circuitStates.set(operationId, state);
18912
+ }
18913
+ function recordSuccess(operationId) {
18914
+ circuitStates.delete(operationId);
18915
+ }
18916
+ var TRANSIENT_ERROR_CODES, PERMANENT_ERROR_CODES, circuitStates, RetryPolicy, defaultAgentRetryPolicy, defaultToolRetryPolicy;
18917
+ var init_retry = __esm({
18918
+ "core/utils/retry.ts"() {
18919
+ "use strict";
18920
+ TRANSIENT_ERROR_CODES = /* @__PURE__ */ new Set([
18921
+ "EBUSY",
18922
+ // Resource busy
18923
+ "EAGAIN",
18924
+ // Resource temporarily unavailable
18925
+ "ETIMEDOUT",
18926
+ // Operation timed out
18927
+ "ECONNRESET",
18928
+ // Connection reset by peer
18929
+ "ECONNREFUSED",
18930
+ // Connection refused (may be temporary)
18931
+ "ENOTFOUND",
18932
+ // DNS lookup failed (may be temporary)
18933
+ "EAI_AGAIN"
18934
+ // DNS temporary failure
18935
+ ]);
18936
+ PERMANENT_ERROR_CODES = /* @__PURE__ */ new Set([
18937
+ "ENOENT",
18938
+ // No such file or directory
18939
+ "EACCES",
18940
+ // Permission denied
18941
+ "EPERM",
18942
+ // Operation not permitted
18943
+ "EISDIR",
18944
+ // Is a directory
18945
+ "ENOTDIR",
18946
+ // Not a directory
18947
+ "EINVAL"
18948
+ // Invalid argument
18949
+ ]);
18950
+ __name(isTransientError, "isTransientError");
18951
+ __name(isPermanentError, "isPermanentError");
18952
+ circuitStates = /* @__PURE__ */ new Map();
18953
+ __name(isCircuitOpen, "isCircuitOpen");
18954
+ __name(recordFailure, "recordFailure");
18955
+ __name(recordSuccess, "recordSuccess");
18956
+ RetryPolicy = class {
18957
+ static {
18958
+ __name(this, "RetryPolicy");
18959
+ }
18960
+ options;
18961
+ constructor(options = {}) {
18962
+ this.options = {
18963
+ maxAttempts: options.maxAttempts ?? 3,
18964
+ baseDelayMs: options.baseDelayMs ?? 1e3,
18965
+ maxDelayMs: options.maxDelayMs ?? 8e3,
18966
+ circuitBreakerThreshold: options.circuitBreakerThreshold ?? 5,
18967
+ circuitBreakerTimeoutMs: options.circuitBreakerTimeoutMs ?? 6e4
18968
+ };
18969
+ }
18970
+ /**
18971
+ * Execute an operation with retry logic
18972
+ *
18973
+ * @param operation - Async function to execute
18974
+ * @param operationId - Optional ID for circuit breaker tracking
18975
+ * @returns Result of the operation
18976
+ * @throws Error if all attempts fail or circuit is open
18977
+ */
18978
+ async execute(operation, operationId = "default") {
18979
+ if (isCircuitOpen(
18980
+ operationId,
18981
+ this.options.circuitBreakerThreshold,
18982
+ this.options.circuitBreakerTimeoutMs
18983
+ )) {
18984
+ throw new Error(
18985
+ `Circuit breaker is open for operation: ${operationId}. Too many consecutive failures.`
18986
+ );
18987
+ }
18988
+ let lastError;
18989
+ let attempt = 0;
18990
+ while (attempt < this.options.maxAttempts) {
18991
+ try {
18992
+ const result = await operation();
18993
+ recordSuccess(operationId);
18994
+ return result;
18995
+ } catch (error) {
18996
+ lastError = error;
18997
+ attempt++;
18998
+ if (isPermanentError(error)) {
18999
+ recordFailure(operationId, this.options.circuitBreakerThreshold);
19000
+ throw error;
19001
+ }
19002
+ const shouldRetry = isTransientError(error) && attempt < this.options.maxAttempts;
19003
+ if (!shouldRetry) {
19004
+ recordFailure(operationId, this.options.circuitBreakerThreshold);
19005
+ throw error;
19006
+ }
19007
+ const delay = Math.min(
19008
+ this.options.baseDelayMs * 2 ** (attempt - 1),
19009
+ this.options.maxDelayMs
19010
+ );
19011
+ await new Promise((resolve) => setTimeout(resolve, delay));
19012
+ }
19013
+ }
19014
+ recordFailure(operationId, this.options.circuitBreakerThreshold);
19015
+ throw lastError;
19016
+ }
19017
+ /**
19018
+ * Check if an error is transient (exposed for testing)
19019
+ */
19020
+ isTransientError(error) {
19021
+ return isTransientError(error);
19022
+ }
19023
+ /**
19024
+ * Check if circuit is open for an operation (exposed for testing)
19025
+ */
19026
+ isCircuitOpen(operationId) {
19027
+ return isCircuitOpen(
19028
+ operationId,
19029
+ this.options.circuitBreakerThreshold,
19030
+ this.options.circuitBreakerTimeoutMs
19031
+ );
19032
+ }
19033
+ /**
19034
+ * Get current circuit state for an operation (exposed for testing)
19035
+ */
19036
+ getCircuitState(operationId) {
19037
+ return circuitStates.get(operationId);
19038
+ }
19039
+ /**
19040
+ * Reset circuit breaker for an operation (exposed for testing)
19041
+ */
19042
+ resetCircuit(operationId) {
19043
+ circuitStates.delete(operationId);
19044
+ }
19045
+ /**
19046
+ * Reset all circuit breakers (exposed for testing)
19047
+ */
19048
+ resetAllCircuits() {
19049
+ circuitStates.clear();
19050
+ }
19051
+ };
19052
+ defaultAgentRetryPolicy = new RetryPolicy({
19053
+ maxAttempts: 3,
19054
+ baseDelayMs: 1e3,
19055
+ maxDelayMs: 8e3
19056
+ });
19057
+ defaultToolRetryPolicy = new RetryPolicy({
19058
+ maxAttempts: 2,
19059
+ baseDelayMs: 500,
19060
+ maxDelayMs: 2e3
19061
+ });
19062
+ }
19063
+ });
19064
+
18575
19065
  // core/agentic/tool-registry.ts
18576
19066
  import { exec as exec7 } from "node:child_process";
18577
19067
  import fs36 from "node:fs/promises";
@@ -18580,6 +19070,7 @@ var execAsync4, toolRegistry, tool_registry_default;
18580
19070
  var init_tool_registry = __esm({
18581
19071
  "core/agentic/tool-registry.ts"() {
18582
19072
  "use strict";
19073
+ init_retry();
18583
19074
  execAsync4 = promisify8(exec7);
18584
19075
  toolRegistry = {
18585
19076
  tools: /* @__PURE__ */ new Map(),
@@ -18618,16 +19109,34 @@ var init_tool_registry = __esm({
18618
19109
  };
18619
19110
  toolRegistry.register("Read", async (filePath) => {
18620
19111
  try {
18621
- return await fs36.readFile(filePath, "utf-8");
18622
- } catch (_error) {
19112
+ return await defaultToolRetryPolicy.execute(
19113
+ async () => await fs36.readFile(filePath, "utf-8"),
19114
+ `read-${filePath}`
19115
+ );
19116
+ } catch (error) {
19117
+ if (isPermanentError(error)) {
19118
+ return null;
19119
+ }
19120
+ if (isTransientError(error)) {
19121
+ return null;
19122
+ }
18623
19123
  return null;
18624
19124
  }
18625
19125
  });
18626
19126
  toolRegistry.register("Write", async (filePath, content) => {
18627
19127
  try {
18628
- await fs36.writeFile(filePath, content, "utf-8");
19128
+ await defaultToolRetryPolicy.execute(
19129
+ async () => await fs36.writeFile(filePath, content, "utf-8"),
19130
+ `write-${filePath}`
19131
+ );
18629
19132
  return true;
18630
- } catch (_error) {
19133
+ } catch (error) {
19134
+ if (isPermanentError(error)) {
19135
+ return false;
19136
+ }
19137
+ if (isTransientError(error)) {
19138
+ return false;
19139
+ }
18631
19140
  return false;
18632
19141
  }
18633
19142
  });
@@ -18635,8 +19144,10 @@ var init_tool_registry = __esm({
18635
19144
  "Bash",
18636
19145
  async (command) => {
18637
19146
  try {
18638
- const { stdout, stderr } = await execAsync4(command);
18639
- return { stdout, stderr };
19147
+ return await defaultToolRetryPolicy.execute(
19148
+ async () => await execAsync4(command),
19149
+ `bash-${command}`
19150
+ );
18640
19151
  } catch (error) {
18641
19152
  const err = error;
18642
19153
  return {
@@ -19279,6 +19790,7 @@ var init_agent_generator = __esm({
19279
19790
  "core/services/agent-generator.ts"() {
19280
19791
  "use strict";
19281
19792
  init_preserve_sections();
19793
+ init_retry();
19282
19794
  }
19283
19795
  });
19284
19796
 
@@ -19507,6 +20019,7 @@ var init_agent_service = __esm({
19507
20019
  init_agent_router();
19508
20020
  init_errors();
19509
20021
  init_agent_detector();
20022
+ init_retry();
19510
20023
  init_();
19511
20024
  VALID_AGENT_TYPES = ["claude"];
19512
20025
  AgentService = class {
@@ -19521,20 +20034,23 @@ var init_agent_service = __esm({
19521
20034
  }
19522
20035
  /**
19523
20036
  * Initialize agent (Claude Code, Desktop, or Terminal)
20037
+ * Wrapped with retry policy to handle transient failures
19524
20038
  */
19525
20039
  async initialize() {
19526
20040
  if (this.agent) return this.agent;
19527
- this.agentInfo = await detect2();
19528
- if (!this.agentInfo?.isSupported) {
19529
- throw AgentError.notSupported(this.agentInfo?.type ?? "unknown");
19530
- }
19531
- const agentType = this.agentInfo.type;
19532
- if (!agentType || !VALID_AGENT_TYPES.includes(agentType)) {
19533
- throw AgentError.notSupported(this.agentInfo?.type ?? "unknown");
19534
- }
19535
- const { default: Agent } = await globImport_infrastructure_agent(`../infrastructure/${agentType}-agent`);
19536
- this.agent = new Agent();
19537
- return this.agent;
20041
+ return await defaultAgentRetryPolicy.execute(async () => {
20042
+ this.agentInfo = await detect2();
20043
+ if (!this.agentInfo?.isSupported) {
20044
+ throw AgentError.notSupported(this.agentInfo?.type ?? "unknown");
20045
+ }
20046
+ const agentType = this.agentInfo.type;
20047
+ if (!agentType || !VALID_AGENT_TYPES.includes(agentType)) {
20048
+ throw AgentError.notSupported(this.agentInfo?.type ?? "unknown");
20049
+ }
20050
+ const { default: Agent } = await globImport_infrastructure_agent(`../infrastructure/${agentType}-agent`);
20051
+ this.agent = new Agent();
20052
+ return this.agent;
20053
+ }, "agent-initialization");
19538
20054
  }
19539
20055
  /**
19540
20056
  * Get current agent info
@@ -20764,6 +21280,7 @@ var init_memory_service = __esm({
20764
21280
  "use strict";
20765
21281
  init_config_manager();
20766
21282
  init_path_manager();
21283
+ init_archive_storage();
20767
21284
  init_fs();
20768
21285
  init_date_helper();
20769
21286
  init_jsonl_helper();
@@ -20859,6 +21376,39 @@ var init_memory_service = __esm({
20859
21376
  return [];
20860
21377
  }
20861
21378
  }
21379
+ /**
21380
+ * Cap memory log at max entries (PRJ-267).
21381
+ * Moves overflow entries to archive table, keeps most recent entries.
21382
+ * Returns count of archived entries.
21383
+ */
21384
+ async capEntries(projectId) {
21385
+ try {
21386
+ const memoryPath = path_manager_default.getFilePath(projectId, "memory", "context.jsonl");
21387
+ const entries = await readJsonLines(memoryPath);
21388
+ if (entries.length <= ARCHIVE_POLICIES.MEMORY_MAX_ENTRIES) {
21389
+ return 0;
21390
+ }
21391
+ const overflow = entries.slice(0, entries.length - ARCHIVE_POLICIES.MEMORY_MAX_ENTRIES);
21392
+ const kept = entries.slice(-ARCHIVE_POLICIES.MEMORY_MAX_ENTRIES);
21393
+ archiveStorage.archiveMany(
21394
+ projectId,
21395
+ overflow.map((entry, i) => ({
21396
+ entityType: "memory_entry",
21397
+ entityId: `memory-${entry.timestamp || i}`,
21398
+ entityData: entry,
21399
+ summary: entry.action,
21400
+ reason: "overflow"
21401
+ }))
21402
+ );
21403
+ await writeJsonLines(memoryPath, kept);
21404
+ return overflow.length;
21405
+ } catch (error) {
21406
+ if (!isNotFoundError(error)) {
21407
+ console.error(`Memory cap error: ${getErrorMessage2(error)}`);
21408
+ }
21409
+ return 0;
21410
+ }
21411
+ }
20862
21412
  };
20863
21413
  memoryService = new MemoryService();
20864
21414
  }
@@ -26977,13 +27527,19 @@ var init_sync_service = __esm({
26977
27527
  init_config_manager();
26978
27528
  init_path_manager();
26979
27529
  init_analysis_storage();
27530
+ init_archive_storage();
27531
+ init_ideas_storage();
26980
27532
  init_metrics_storage();
26981
27533
  init_migrate_json();
27534
+ init_queue_storage();
27535
+ init_shipped_storage();
27536
+ init_state_storage();
26982
27537
  init_citations();
26983
27538
  init_date_helper();
26984
27539
  init_logger();
26985
27540
  init_context_generator();
26986
27541
  init_local_state_generator();
27542
+ init_memory_service();
26987
27543
  init_skill_installer();
26988
27544
  init_stack_detector();
26989
27545
  init_sync_verifier();
@@ -27159,6 +27715,7 @@ var init_sync_service = __esm({
27159
27715
  ]);
27160
27716
  const duration = Date.now() - startTime;
27161
27717
  const syncMetrics = await this.recordSyncMetrics(stats, contextFiles, agents, duration);
27718
+ await this.archiveStaleData();
27162
27719
  await command_installer_default.installGlobalConfig();
27163
27720
  await command_installer_default.syncCommands();
27164
27721
  let verification;
@@ -27938,6 +28495,40 @@ You are the ${name} expert for this project. Apply best practices for the detect
27938
28495
  }
27939
28496
  }
27940
28497
  // ==========================================================================
28498
+ // ARCHIVAL (PRJ-267)
28499
+ // ==========================================================================
28500
+ /**
28501
+ * Archive stale data across all storage types.
28502
+ * Runs during sync to keep active storage lean.
28503
+ */
28504
+ async archiveStaleData() {
28505
+ if (!this.projectId) return;
28506
+ try {
28507
+ const [shipped, dormant, staleQueue, stalePaused, memoryCapped] = await Promise.all([
28508
+ shippedStorage.archiveOldShipped(this.projectId).catch(() => 0),
28509
+ ideasStorage.markDormantIdeas(this.projectId).catch(() => 0),
28510
+ queueStorage.removeStaleCompleted(this.projectId).catch(() => 0),
28511
+ stateStorage.archiveStalePausedTasks(this.projectId).catch(() => []),
28512
+ memoryService.capEntries(this.projectId).catch(() => 0)
28513
+ ]);
28514
+ const totalArchived = shipped + dormant + staleQueue + stalePaused.length + memoryCapped;
28515
+ if (totalArchived > 0) {
28516
+ logger_default.info("Archived stale data", {
28517
+ shipped,
28518
+ dormant,
28519
+ staleQueue,
28520
+ stalePaused: stalePaused.length,
28521
+ memoryCapped,
28522
+ total: totalArchived
28523
+ });
28524
+ const stats = archiveStorage.getStats(this.projectId);
28525
+ logger_default.debug("Archive stats", stats);
28526
+ }
28527
+ } catch (error) {
28528
+ logger_default.debug("Archival failed (non-critical)", { error: getErrorMessage(error) });
28529
+ }
28530
+ }
28531
+ // ==========================================================================
27941
28532
  // HELPERS
27942
28533
  // ==========================================================================
27943
28534
  async fileExists(filename) {
@@ -33881,7 +34472,7 @@ var require_package = __commonJS({
33881
34472
  "package.json"(exports, module) {
33882
34473
  module.exports = {
33883
34474
  name: "prjct-cli",
33884
- version: "1.18.0",
34475
+ version: "1.20.0",
33885
34476
  description: "Context layer for AI agents. Project context for Claude Code, Gemini CLI, and more.",
33886
34477
  main: "core/index.ts",
33887
34478
  bin: {