panopticon-cli 0.4.32 → 0.5.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.
Files changed (142) hide show
  1. package/README.md +96 -210
  2. package/dist/{agents-BDFHF4T3.js → agents-E43Y3HNU.js} +10 -7
  3. package/dist/chunk-7SN4L4PH.js +150 -0
  4. package/dist/chunk-7SN4L4PH.js.map +1 -0
  5. package/dist/{chunk-2NIAOCIC.js → chunk-AAFQANKW.js} +358 -97
  6. package/dist/chunk-AAFQANKW.js.map +1 -0
  7. package/dist/chunk-AQXETQHW.js +113 -0
  8. package/dist/chunk-AQXETQHW.js.map +1 -0
  9. package/dist/chunk-B3PF6JPQ.js +212 -0
  10. package/dist/chunk-B3PF6JPQ.js.map +1 -0
  11. package/dist/chunk-CFCUOV3Q.js +669 -0
  12. package/dist/chunk-CFCUOV3Q.js.map +1 -0
  13. package/dist/chunk-CWELWPWQ.js +32 -0
  14. package/dist/chunk-CWELWPWQ.js.map +1 -0
  15. package/dist/chunk-DI7ABPNQ.js +352 -0
  16. package/dist/chunk-DI7ABPNQ.js.map +1 -0
  17. package/dist/{chunk-VU4FLXV5.js → chunk-FQ66DECN.js} +31 -4
  18. package/dist/chunk-FQ66DECN.js.map +1 -0
  19. package/dist/{chunk-VIWUCJ4V.js → chunk-FTCPTHIJ.js} +57 -432
  20. package/dist/chunk-FTCPTHIJ.js.map +1 -0
  21. package/dist/{review-status-GWQYY77L.js → chunk-GFP3PIPB.js} +14 -7
  22. package/dist/chunk-GFP3PIPB.js.map +1 -0
  23. package/dist/chunk-GR6ZZMCX.js +816 -0
  24. package/dist/chunk-GR6ZZMCX.js.map +1 -0
  25. package/dist/chunk-HJSM6E6U.js +1038 -0
  26. package/dist/chunk-HJSM6E6U.js.map +1 -0
  27. package/dist/{chunk-XP2DXWYP.js → chunk-HZT2AOPN.js} +164 -39
  28. package/dist/chunk-HZT2AOPN.js.map +1 -0
  29. package/dist/chunk-JQBV3Q2W.js +29 -0
  30. package/dist/chunk-JQBV3Q2W.js.map +1 -0
  31. package/dist/{chunk-BWGFN44T.js → chunk-JT4O4YVM.js} +28 -16
  32. package/dist/chunk-JT4O4YVM.js.map +1 -0
  33. package/dist/chunk-NTO3EDB3.js +600 -0
  34. package/dist/chunk-NTO3EDB3.js.map +1 -0
  35. package/dist/{chunk-JY7R7V4G.js → chunk-OMNXYPXC.js} +2 -2
  36. package/dist/chunk-OMNXYPXC.js.map +1 -0
  37. package/dist/chunk-PELXV435.js +215 -0
  38. package/dist/chunk-PELXV435.js.map +1 -0
  39. package/dist/chunk-PPRFKTVC.js +154 -0
  40. package/dist/chunk-PPRFKTVC.js.map +1 -0
  41. package/dist/chunk-WQG2TYCB.js +677 -0
  42. package/dist/chunk-WQG2TYCB.js.map +1 -0
  43. package/dist/{chunk-HCTJFIJJ.js → chunk-YLPSQAM2.js} +2 -2
  44. package/dist/{chunk-HCTJFIJJ.js.map → chunk-YLPSQAM2.js.map} +1 -1
  45. package/dist/{chunk-6HXKTOD7.js → chunk-ZTFNYOC7.js} +53 -38
  46. package/dist/chunk-ZTFNYOC7.js.map +1 -0
  47. package/dist/cli/index.js +5103 -3165
  48. package/dist/cli/index.js.map +1 -1
  49. package/dist/{config-BOAMSKTF.js → config-4CJNUE3O.js} +7 -3
  50. package/dist/dashboard/prompts/merge-agent.md +217 -0
  51. package/dist/dashboard/prompts/review-agent.md +409 -0
  52. package/dist/dashboard/prompts/sync-main.md +84 -0
  53. package/dist/dashboard/prompts/test-agent.md +283 -0
  54. package/dist/dashboard/prompts/work-agent.md +249 -0
  55. package/dist/dashboard/public/assets/index-BxpjweAL.css +32 -0
  56. package/dist/dashboard/public/assets/index-DQHkwvvJ.js +743 -0
  57. package/dist/dashboard/public/index.html +2 -2
  58. package/dist/dashboard/server.js +17619 -4044
  59. package/dist/{dns-L3L2BB27.js → dns-7BDJSD3E.js} +4 -2
  60. package/dist/{feedback-writer-AAKF5BTK.js → feedback-writer-LVZ5TFYZ.js} +8 -4
  61. package/dist/feedback-writer-LVZ5TFYZ.js.map +1 -0
  62. package/dist/hume-WMAUBBV2.js +13 -0
  63. package/dist/index.d.ts +162 -40
  64. package/dist/index.js +67 -23
  65. package/dist/index.js.map +1 -1
  66. package/dist/{projects-VXRUCMLM.js → projects-JEIVIYC6.js} +3 -3
  67. package/dist/rally-RKFSWC7E.js +10 -0
  68. package/dist/{remote-agents-Z3R2A5BN.js → remote-agents-TFSMW7GN.js} +2 -2
  69. package/dist/{remote-workspace-2G6V2KNP.js → remote-workspace-AHVHQEES.js} +8 -8
  70. package/dist/review-status-EPFG4XM7.js +19 -0
  71. package/dist/shadow-state-5MDP6YXH.js +30 -0
  72. package/dist/shadow-state-5MDP6YXH.js.map +1 -0
  73. package/dist/{specialist-context-N32QBNNQ.js → specialist-context-ZC6A4M3I.js} +8 -7
  74. package/dist/{specialist-context-N32QBNNQ.js.map → specialist-context-ZC6A4M3I.js.map} +1 -1
  75. package/dist/{specialist-logs-GF3YV4KL.js → specialist-logs-KLGJCEUL.js} +7 -6
  76. package/dist/specialist-logs-KLGJCEUL.js.map +1 -0
  77. package/dist/{specialists-JBIW6MP4.js → specialists-O4HWDJL5.js} +7 -6
  78. package/dist/specialists-O4HWDJL5.js.map +1 -0
  79. package/dist/tldr-daemon-T3THOUGT.js +21 -0
  80. package/dist/tldr-daemon-T3THOUGT.js.map +1 -0
  81. package/dist/traefik-QN7R5I6V.js +19 -0
  82. package/dist/traefik-QN7R5I6V.js.map +1 -0
  83. package/dist/tunnel-W2GZBLEV.js +13 -0
  84. package/dist/tunnel-W2GZBLEV.js.map +1 -0
  85. package/dist/workspace-manager-IE4JL2JP.js +22 -0
  86. package/dist/workspace-manager-IE4JL2JP.js.map +1 -0
  87. package/package.json +2 -2
  88. package/scripts/heartbeat-hook +37 -10
  89. package/scripts/patches/llm-tldr-tsx-support.py +109 -0
  90. package/scripts/pre-tool-hook +26 -15
  91. package/scripts/record-cost-event.js +177 -43
  92. package/scripts/record-cost-event.ts +87 -3
  93. package/scripts/statusline.sh +169 -0
  94. package/scripts/stop-hook +21 -11
  95. package/scripts/tldr-post-edit +72 -0
  96. package/scripts/tldr-read-enforcer +275 -0
  97. package/scripts/work-agent-stop-hook +137 -0
  98. package/skills/check-merged/SKILL.md +143 -0
  99. package/skills/crash-investigation/SKILL.md +301 -0
  100. package/skills/github-cli/SKILL.md +185 -0
  101. package/skills/myn-standards/SKILL.md +351 -0
  102. package/skills/pan-reopen/SKILL.md +65 -0
  103. package/skills/pan-sync-main/SKILL.md +87 -0
  104. package/skills/pan-tldr/SKILL.md +149 -0
  105. package/skills/react-best-practices/SKILL.md +125 -0
  106. package/skills/spec-readiness/REPORT-TEMPLATE.md +158 -0
  107. package/skills/spec-readiness/SCORING-REFERENCE.md +369 -0
  108. package/skills/spec-readiness/SKILL.md +400 -0
  109. package/skills/spec-readiness-setup/SKILL.md +361 -0
  110. package/skills/workspace-status/SKILL.md +56 -0
  111. package/skills/write-spec/SKILL.md +138 -0
  112. package/templates/traefik/dynamic/panopticon.yml.template +0 -5
  113. package/templates/traefik/traefik.yml +0 -8
  114. package/dist/chunk-2NIAOCIC.js.map +0 -1
  115. package/dist/chunk-3XAB4IXF.js +0 -51
  116. package/dist/chunk-3XAB4IXF.js.map +0 -1
  117. package/dist/chunk-6HXKTOD7.js.map +0 -1
  118. package/dist/chunk-BBCUK6N2.js +0 -241
  119. package/dist/chunk-BBCUK6N2.js.map +0 -1
  120. package/dist/chunk-BWGFN44T.js.map +0 -1
  121. package/dist/chunk-ELK6Q7QI.js +0 -545
  122. package/dist/chunk-ELK6Q7QI.js.map +0 -1
  123. package/dist/chunk-JY7R7V4G.js.map +0 -1
  124. package/dist/chunk-LYSBSZYV.js +0 -1523
  125. package/dist/chunk-LYSBSZYV.js.map +0 -1
  126. package/dist/chunk-VIWUCJ4V.js.map +0 -1
  127. package/dist/chunk-VU4FLXV5.js.map +0 -1
  128. package/dist/chunk-XP2DXWYP.js.map +0 -1
  129. package/dist/dashboard/public/assets/index-C7X6LP5Z.css +0 -32
  130. package/dist/dashboard/public/assets/index-ClYqpcAJ.js +0 -645
  131. package/dist/feedback-writer-AAKF5BTK.js.map +0 -1
  132. package/dist/review-status-GWQYY77L.js.map +0 -1
  133. package/dist/traefik-CUJM6K5Z.js +0 -12
  134. /package/dist/{agents-BDFHF4T3.js.map → agents-E43Y3HNU.js.map} +0 -0
  135. /package/dist/{config-BOAMSKTF.js.map → config-4CJNUE3O.js.map} +0 -0
  136. /package/dist/{dns-L3L2BB27.js.map → dns-7BDJSD3E.js.map} +0 -0
  137. /package/dist/{projects-VXRUCMLM.js.map → hume-WMAUBBV2.js.map} +0 -0
  138. /package/dist/{remote-agents-Z3R2A5BN.js.map → projects-JEIVIYC6.js.map} +0 -0
  139. /package/dist/{specialist-logs-GF3YV4KL.js.map → rally-RKFSWC7E.js.map} +0 -0
  140. /package/dist/{specialists-JBIW6MP4.js.map → remote-agents-TFSMW7GN.js.map} +0 -0
  141. /package/dist/{remote-workspace-2G6V2KNP.js.map → remote-workspace-AHVHQEES.js.map} +0 -0
  142. /package/dist/{traefik-CUJM6K5Z.js.map → review-status-EPFG4XM7.js.map} +0 -0
@@ -0,0 +1,212 @@
1
+ import {
2
+ init_esm_shims
3
+ } from "./chunk-ZHC57RCV.js";
4
+
5
+ // src/lib/shadow-state.ts
6
+ init_esm_shims();
7
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, unlinkSync } from "fs";
8
+ import { join } from "path";
9
+ import { homedir } from "os";
10
+ var SHADOW_STATE_DIR = join(homedir(), ".panopticon", "shadow-state");
11
+ function ensureShadowStateDir() {
12
+ if (!existsSync(SHADOW_STATE_DIR)) {
13
+ mkdirSync(SHADOW_STATE_DIR, { recursive: true });
14
+ }
15
+ }
16
+ function getShadowStatePath(issueId) {
17
+ const normalizedId = issueId.toUpperCase().replace(/[^A-Z0-9-]/g, "");
18
+ return join(SHADOW_STATE_DIR, `${normalizedId}.json`);
19
+ }
20
+ function getShadowState(issueId) {
21
+ const filePath = getShadowStatePath(issueId);
22
+ if (!existsSync(filePath)) {
23
+ return null;
24
+ }
25
+ try {
26
+ const content = readFileSync(filePath, "utf-8");
27
+ return JSON.parse(content);
28
+ } catch (error) {
29
+ console.error(`Error reading shadow state for ${issueId}:`, error);
30
+ return null;
31
+ }
32
+ }
33
+ function isShadowed(issueId) {
34
+ return getShadowState(issueId) !== null;
35
+ }
36
+ function createShadowState(issueId, initialTrackerStatus = "open", triggeredBy = "unknown") {
37
+ ensureShadowStateDir();
38
+ const now = (/* @__PURE__ */ new Date()).toISOString();
39
+ const shadowState = {
40
+ issueId: issueId.toUpperCase(),
41
+ shadowStatus: initialTrackerStatus,
42
+ trackerStatus: initialTrackerStatus,
43
+ trackerStatusUpdatedAt: now,
44
+ shadowedAt: now,
45
+ history: []
46
+ };
47
+ const filePath = getShadowStatePath(issueId);
48
+ writeFileSync(filePath, JSON.stringify(shadowState, null, 2), "utf-8");
49
+ return shadowState;
50
+ }
51
+ function updateShadowState(issueId, newStatus, triggeredBy, targetCanonicalState) {
52
+ ensureShadowStateDir();
53
+ let state = getShadowState(issueId);
54
+ if (!state) {
55
+ state = {
56
+ issueId: issueId.toUpperCase(),
57
+ shadowStatus: newStatus,
58
+ targetCanonicalState,
59
+ trackerStatus: newStatus,
60
+ trackerStatusUpdatedAt: (/* @__PURE__ */ new Date()).toISOString(),
61
+ shadowedAt: (/* @__PURE__ */ new Date()).toISOString(),
62
+ history: []
63
+ };
64
+ }
65
+ if (state.shadowStatus !== newStatus) {
66
+ const transition = {
67
+ from: state.shadowStatus,
68
+ to: newStatus,
69
+ at: (/* @__PURE__ */ new Date()).toISOString(),
70
+ by: triggeredBy,
71
+ syncedToTracker: false
72
+ };
73
+ state.history.push(transition);
74
+ state.shadowStatus = newStatus;
75
+ }
76
+ if (targetCanonicalState) {
77
+ state.targetCanonicalState = targetCanonicalState;
78
+ }
79
+ const filePath = getShadowStatePath(issueId);
80
+ writeFileSync(filePath, JSON.stringify(state, null, 2), "utf-8");
81
+ return state;
82
+ }
83
+ function updateTrackerStatusCache(issueId, trackerStatus) {
84
+ const state = getShadowState(issueId);
85
+ if (!state) {
86
+ throw new Error(`Cannot update tracker status: ${issueId} is not in shadow mode`);
87
+ }
88
+ state.trackerStatus = trackerStatus;
89
+ state.trackerStatusUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
90
+ const filePath = getShadowStatePath(issueId);
91
+ writeFileSync(filePath, JSON.stringify(state, null, 2), "utf-8");
92
+ return state;
93
+ }
94
+ function markAsSynced(issueId, syncedState, previousTrackerState) {
95
+ const state = getShadowState(issueId);
96
+ if (!state) {
97
+ return {
98
+ success: false,
99
+ error: `Issue ${issueId} is not in shadow mode`
100
+ };
101
+ }
102
+ const now = (/* @__PURE__ */ new Date()).toISOString();
103
+ let entriesSynced = 0;
104
+ for (const entry of state.history) {
105
+ if (!entry.syncedToTracker) {
106
+ entry.syncedToTracker = true;
107
+ entriesSynced++;
108
+ }
109
+ }
110
+ state.syncedAt = now;
111
+ state.trackerStatus = syncedState;
112
+ state.trackerStatusUpdatedAt = now;
113
+ const filePath = getShadowStatePath(issueId);
114
+ writeFileSync(filePath, JSON.stringify(state, null, 2), "utf-8");
115
+ return {
116
+ success: true,
117
+ syncedState,
118
+ previousState: previousTrackerState,
119
+ entriesSynced
120
+ };
121
+ }
122
+ function listShadowedIssues() {
123
+ if (!existsSync(SHADOW_STATE_DIR)) {
124
+ return [];
125
+ }
126
+ const files = readdirSync(SHADOW_STATE_DIR);
127
+ const states = [];
128
+ for (const file of files) {
129
+ if (!file.endsWith(".json")) continue;
130
+ try {
131
+ const content = readFileSync(join(SHADOW_STATE_DIR, file), "utf-8");
132
+ const state = JSON.parse(content);
133
+ states.push(state);
134
+ } catch (error) {
135
+ console.error(`Error reading shadow state file ${file}:`, error);
136
+ }
137
+ }
138
+ return states.sort(
139
+ (a, b) => new Date(b.shadowedAt).getTime() - new Date(a.shadowedAt).getTime()
140
+ );
141
+ }
142
+ function removeShadowState(issueId, syncFirst = false) {
143
+ const filePath = getShadowStatePath(issueId);
144
+ if (!existsSync(filePath)) {
145
+ return {
146
+ success: false,
147
+ error: `Issue ${issueId} is not in shadow mode`
148
+ };
149
+ }
150
+ try {
151
+ unlinkSync(filePath);
152
+ return {
153
+ success: true,
154
+ synced: syncFirst
155
+ };
156
+ } catch (error) {
157
+ return {
158
+ success: false,
159
+ error: `Failed to remove shadow state: ${error.message}`
160
+ };
161
+ }
162
+ }
163
+ function getDisplayStatus(issueId, trackerStatus) {
164
+ const state = getShadowState(issueId);
165
+ if (!state) {
166
+ return {
167
+ status: trackerStatus,
168
+ isShadowed: false
169
+ };
170
+ }
171
+ return {
172
+ status: state.shadowStatus,
173
+ isShadowed: true,
174
+ trackerStatus: state.trackerStatus,
175
+ outOfSync: state.shadowStatus !== state.trackerStatus
176
+ };
177
+ }
178
+ function needsSync(issueId) {
179
+ const state = getShadowState(issueId);
180
+ if (!state) {
181
+ return false;
182
+ }
183
+ return state.shadowStatus !== state.trackerStatus;
184
+ }
185
+ function getUnsyncedHistory(issueId) {
186
+ const state = getShadowState(issueId);
187
+ if (!state) {
188
+ return [];
189
+ }
190
+ return state.history.filter((entry) => !entry.syncedToTracker);
191
+ }
192
+ function getPendingSyncCount() {
193
+ return listShadowedIssues().filter(
194
+ (state) => state.shadowStatus !== state.trackerStatus
195
+ ).length;
196
+ }
197
+
198
+ export {
199
+ getShadowState,
200
+ isShadowed,
201
+ createShadowState,
202
+ updateShadowState,
203
+ updateTrackerStatusCache,
204
+ markAsSynced,
205
+ listShadowedIssues,
206
+ removeShadowState,
207
+ getDisplayStatus,
208
+ needsSync,
209
+ getUnsyncedHistory,
210
+ getPendingSyncCount
211
+ };
212
+ //# sourceMappingURL=chunk-B3PF6JPQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/shadow-state.ts"],"sourcesContent":["/**\n * Shadow State Storage Module\n *\n * Manages shadow state for issues - tracking status locally without updating\n * the issue tracker until explicitly synced.\n *\n * Storage Location: ~/.panopticon/shadow-state/\n */\n\nimport { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport type { IssueState } from './tracker/interface.js';\n\n// Storage directory for shadow state files\nconst SHADOW_STATE_DIR = join(homedir(), '.panopticon', 'shadow-state');\n\n/**\n * Shadow history entry - tracks state transitions\n */\nexport interface ShadowHistoryEntry {\n /** Previous state */\n from: IssueState;\n /** New state */\n to: IssueState;\n /** When the transition occurred */\n at: string;\n /** Command that triggered the transition (e.g., \"pan work plan\", \"dashboard\") */\n by: string;\n /** Whether this transition was synced to the tracker */\n syncedToTracker: boolean;\n}\n\n/**\n * Canonical state for Kanban column placement\n */\nexport type CanonicalState = 'backlog' | 'todo' | 'in_progress' | 'in_review' | 'done' | 'canceled';\n\n/**\n * Shadow state for an issue\n */\nexport interface ShadowState {\n /** Issue ID (e.g., \"MIN-123\") */\n issueId: string;\n /** Panopticon's view of the issue status */\n shadowStatus: IssueState;\n /** Target canonical state for Kanban column placement */\n targetCanonicalState?: CanonicalState;\n /** Last known tracker status (cached) */\n trackerStatus: IssueState;\n /** When tracker status was last fetched */\n trackerStatusUpdatedAt: string;\n /** When shadow mode was enabled for this issue */\n shadowedAt: string;\n /** When shadow state was last synced to tracker */\n syncedAt?: string;\n /** Audit trail of state transitions */\n history: ShadowHistoryEntry[];\n}\n\n/**\n * Result of a sync operation\n */\nexport interface SyncResult {\n success: boolean;\n /** The state that was synced */\n syncedState?: IssueState;\n /** Previous tracker state */\n previousState?: IssueState;\n /** Error message if sync failed */\n error?: string;\n /** Number of history entries marked as synced */\n entriesSynced?: number;\n}\n\n/**\n * Ensure the shadow state directory exists\n */\nfunction ensureShadowStateDir(): void {\n if (!existsSync(SHADOW_STATE_DIR)) {\n mkdirSync(SHADOW_STATE_DIR, { recursive: true });\n }\n}\n\n/**\n * Get the file path for a shadow state file\n */\nfunction getShadowStatePath(issueId: string): string {\n // Normalize issue ID for filename (uppercase, replace special chars)\n const normalizedId = issueId.toUpperCase().replace(/[^A-Z0-9-]/g, '');\n return join(SHADOW_STATE_DIR, `${normalizedId}.json`);\n}\n\n/**\n * Get shadow state for an issue\n * @returns ShadowState or null if not shadowed\n */\nexport function getShadowState(issueId: string): ShadowState | null {\n const filePath = getShadowStatePath(issueId);\n\n if (!existsSync(filePath)) {\n return null;\n }\n\n try {\n const content = readFileSync(filePath, 'utf-8');\n return JSON.parse(content) as ShadowState;\n } catch (error) {\n console.error(`Error reading shadow state for ${issueId}:`, error);\n return null;\n }\n}\n\n/**\n * Check if an issue is in shadow mode\n */\nexport function isShadowed(issueId: string): boolean {\n return getShadowState(issueId) !== null;\n}\n\n/**\n * Create a new shadow state for an issue\n */\nexport function createShadowState(\n issueId: string,\n initialTrackerStatus: IssueState = 'open',\n triggeredBy: string = 'unknown'\n): ShadowState {\n ensureShadowStateDir();\n\n const now = new Date().toISOString();\n\n const shadowState: ShadowState = {\n issueId: issueId.toUpperCase(),\n shadowStatus: initialTrackerStatus,\n trackerStatus: initialTrackerStatus,\n trackerStatusUpdatedAt: now,\n shadowedAt: now,\n history: [],\n };\n\n const filePath = getShadowStatePath(issueId);\n writeFileSync(filePath, JSON.stringify(shadowState, null, 2), 'utf-8');\n\n return shadowState;\n}\n\n/**\n * Update shadow state for an issue\n */\nexport function updateShadowState(\n issueId: string,\n newStatus: IssueState,\n triggeredBy: string,\n targetCanonicalState?: CanonicalState\n): ShadowState {\n ensureShadowStateDir();\n\n let state = getShadowState(issueId);\n\n // Create new shadow state if it doesn't exist\n if (!state) {\n state = {\n issueId: issueId.toUpperCase(),\n shadowStatus: newStatus,\n targetCanonicalState,\n trackerStatus: newStatus,\n trackerStatusUpdatedAt: new Date().toISOString(),\n shadowedAt: new Date().toISOString(),\n history: [],\n };\n }\n\n // Only record transition if status changed\n if (state.shadowStatus !== newStatus) {\n const transition: ShadowHistoryEntry = {\n from: state.shadowStatus,\n to: newStatus,\n at: new Date().toISOString(),\n by: triggeredBy,\n syncedToTracker: false,\n };\n\n state.history.push(transition);\n state.shadowStatus = newStatus;\n }\n\n // Always update target canonical state if provided\n if (targetCanonicalState) {\n state.targetCanonicalState = targetCanonicalState;\n }\n\n const filePath = getShadowStatePath(issueId);\n writeFileSync(filePath, JSON.stringify(state, null, 2), 'utf-8');\n\n return state;\n}\n\n/**\n * Update tracker status cache (refresh from tracker)\n */\nexport function updateTrackerStatusCache(\n issueId: string,\n trackerStatus: IssueState\n): ShadowState {\n const state = getShadowState(issueId);\n\n if (!state) {\n throw new Error(`Cannot update tracker status: ${issueId} is not in shadow mode`);\n }\n\n state.trackerStatus = trackerStatus;\n state.trackerStatusUpdatedAt = new Date().toISOString();\n\n const filePath = getShadowStatePath(issueId);\n writeFileSync(filePath, JSON.stringify(state, null, 2), 'utf-8');\n\n return state;\n}\n\n/**\n * Sync shadow state to tracker (mark as synced)\n * This is called after successfully updating the tracker\n */\nexport function markAsSynced(\n issueId: string,\n syncedState: IssueState,\n previousTrackerState?: IssueState\n): SyncResult {\n const state = getShadowState(issueId);\n\n if (!state) {\n return {\n success: false,\n error: `Issue ${issueId} is not in shadow mode`,\n };\n }\n\n const now = new Date().toISOString();\n let entriesSynced = 0;\n\n // Mark all unsynced history entries as synced\n for (const entry of state.history) {\n if (!entry.syncedToTracker) {\n entry.syncedToTracker = true;\n entriesSynced++;\n }\n }\n\n // Update sync timestamp and tracker status\n state.syncedAt = now;\n state.trackerStatus = syncedState;\n state.trackerStatusUpdatedAt = now;\n\n const filePath = getShadowStatePath(issueId);\n writeFileSync(filePath, JSON.stringify(state, null, 2), 'utf-8');\n\n return {\n success: true,\n syncedState,\n previousState: previousTrackerState,\n entriesSynced,\n };\n}\n\n/**\n * List all shadowed issues\n */\nexport function listShadowedIssues(): ShadowState[] {\n if (!existsSync(SHADOW_STATE_DIR)) {\n return [];\n }\n\n const files = readdirSync(SHADOW_STATE_DIR);\n const states: ShadowState[] = [];\n\n for (const file of files) {\n if (!file.endsWith('.json')) continue;\n\n try {\n const content = readFileSync(join(SHADOW_STATE_DIR, file), 'utf-8');\n const state = JSON.parse(content) as ShadowState;\n states.push(state);\n } catch (error) {\n console.error(`Error reading shadow state file ${file}:`, error);\n }\n }\n\n // Sort by shadowedAt (newest first)\n return states.sort((a, b) =>\n new Date(b.shadowedAt).getTime() - new Date(a.shadowedAt).getTime()\n );\n}\n\n/**\n * Remove shadow state for an issue (unshadow)\n * @param syncFirst - If true, attempts to sync to tracker before removing\n */\nexport function removeShadowState(\n issueId: string,\n syncFirst: boolean = false\n): { success: boolean; error?: string; synced?: boolean } {\n const filePath = getShadowStatePath(issueId);\n\n if (!existsSync(filePath)) {\n return {\n success: false,\n error: `Issue ${issueId} is not in shadow mode`,\n };\n }\n\n try {\n // If syncFirst is true, we should have already synced by this point\n // This parameter is just for API clarity\n\n unlinkSync(filePath);\n return {\n success: true,\n synced: syncFirst,\n };\n } catch (error: any) {\n return {\n success: false,\n error: `Failed to remove shadow state: ${error.message}`,\n };\n }\n}\n\n/**\n * Get the display status for an issue\n * Returns shadow status with tracker status info if in shadow mode\n */\nexport function getDisplayStatus(\n issueId: string,\n trackerStatus: IssueState\n): {\n status: IssueState;\n isShadowed: boolean;\n trackerStatus?: IssueState;\n outOfSync?: boolean;\n} {\n const state = getShadowState(issueId);\n\n if (!state) {\n return {\n status: trackerStatus,\n isShadowed: false,\n };\n }\n\n return {\n status: state.shadowStatus,\n isShadowed: true,\n trackerStatus: state.trackerStatus,\n outOfSync: state.shadowStatus !== state.trackerStatus,\n };\n}\n\n/**\n * Check if an issue needs to be synced to tracker\n * (shadow status differs from tracker status)\n */\nexport function needsSync(issueId: string): boolean {\n const state = getShadowState(issueId);\n\n if (!state) {\n return false;\n }\n\n return state.shadowStatus !== state.trackerStatus;\n}\n\n/**\n * Get unsynced history entries for an issue\n */\nexport function getUnsyncedHistory(issueId: string): ShadowHistoryEntry[] {\n const state = getShadowState(issueId);\n\n if (!state) {\n return [];\n }\n\n return state.history.filter(entry => !entry.syncedToTracker);\n}\n\n/**\n * Get the count of issues that need sync\n */\nexport function getPendingSyncCount(): number {\n return listShadowedIssues().filter(state =>\n state.shadowStatus !== state.trackerStatus\n ).length;\n}\n"],"mappings":"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","names":[]}