@planu/cli 1.11.0 → 1.12.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 (179) hide show
  1. package/dist/config/license-plans.json +27 -2
  2. package/dist/engine/api-compat/compatibility-checker.d.ts +4 -0
  3. package/dist/engine/api-compat/compatibility-checker.d.ts.map +1 -0
  4. package/dist/engine/api-compat/compatibility-checker.js +118 -0
  5. package/dist/engine/api-compat/compatibility-checker.js.map +1 -0
  6. package/dist/engine/checkpoint/checkpoint-manager.d.ts +22 -0
  7. package/dist/engine/checkpoint/checkpoint-manager.d.ts.map +1 -0
  8. package/dist/engine/checkpoint/checkpoint-manager.js +76 -0
  9. package/dist/engine/checkpoint/checkpoint-manager.js.map +1 -0
  10. package/dist/engine/checkpoint/policy-engine.d.ts +10 -0
  11. package/dist/engine/checkpoint/policy-engine.d.ts.map +1 -0
  12. package/dist/engine/checkpoint/policy-engine.js +87 -0
  13. package/dist/engine/checkpoint/policy-engine.js.map +1 -0
  14. package/dist/engine/compliance/auto-remediator.d.ts +9 -0
  15. package/dist/engine/compliance/auto-remediator.d.ts.map +1 -0
  16. package/dist/engine/compliance/auto-remediator.js +118 -0
  17. package/dist/engine/compliance/auto-remediator.js.map +1 -0
  18. package/dist/engine/context-profile/profile-catalog.d.ts +5 -0
  19. package/dist/engine/context-profile/profile-catalog.d.ts.map +1 -0
  20. package/dist/engine/context-profile/profile-catalog.js +145 -0
  21. package/dist/engine/context-profile/profile-catalog.js.map +1 -0
  22. package/dist/engine/critical-path/path-analyzer.d.ts +3 -0
  23. package/dist/engine/critical-path/path-analyzer.d.ts.map +1 -0
  24. package/dist/engine/critical-path/path-analyzer.js +145 -0
  25. package/dist/engine/critical-path/path-analyzer.js.map +1 -0
  26. package/dist/engine/drift/violation-resolver.d.ts +9 -0
  27. package/dist/engine/drift/violation-resolver.d.ts.map +1 -0
  28. package/dist/engine/drift/violation-resolver.js +128 -0
  29. package/dist/engine/drift/violation-resolver.js.map +1 -0
  30. package/dist/engine/ears/criterion-scorer.d.ts +7 -0
  31. package/dist/engine/ears/criterion-scorer.d.ts.map +1 -0
  32. package/dist/engine/ears/criterion-scorer.js +87 -0
  33. package/dist/engine/ears/criterion-scorer.js.map +1 -0
  34. package/dist/engine/ears/pattern-matcher.d.ts +5 -0
  35. package/dist/engine/ears/pattern-matcher.d.ts.map +1 -0
  36. package/dist/engine/ears/pattern-matcher.js +48 -0
  37. package/dist/engine/ears/pattern-matcher.js.map +1 -0
  38. package/dist/engine/ears/rewriter.d.ts +7 -0
  39. package/dist/engine/ears/rewriter.d.ts.map +1 -0
  40. package/dist/engine/ears/rewriter.js +45 -0
  41. package/dist/engine/ears/rewriter.js.map +1 -0
  42. package/dist/engine/ears/spec-linter.d.ts +7 -0
  43. package/dist/engine/ears/spec-linter.d.ts.map +1 -0
  44. package/dist/engine/ears/spec-linter.js +127 -0
  45. package/dist/engine/ears/spec-linter.js.map +1 -0
  46. package/dist/engine/health/auto-fixer.d.ts +7 -0
  47. package/dist/engine/health/auto-fixer.d.ts.map +1 -0
  48. package/dist/engine/health/auto-fixer.js +130 -0
  49. package/dist/engine/health/auto-fixer.js.map +1 -0
  50. package/dist/engine/mcp-catalog/catalog-advisor.d.ts +3 -0
  51. package/dist/engine/mcp-catalog/catalog-advisor.d.ts.map +1 -0
  52. package/dist/engine/mcp-catalog/catalog-advisor.js +180 -0
  53. package/dist/engine/mcp-catalog/catalog-advisor.js.map +1 -0
  54. package/dist/engine/similar-problems/similarity-finder.d.ts +3 -0
  55. package/dist/engine/similar-problems/similarity-finder.d.ts.map +1 -0
  56. package/dist/engine/similar-problems/similarity-finder.js +144 -0
  57. package/dist/engine/similar-problems/similarity-finder.js.map +1 -0
  58. package/dist/engine/sync/asana-puller.d.ts +9 -0
  59. package/dist/engine/sync/asana-puller.d.ts.map +1 -0
  60. package/dist/engine/sync/asana-puller.js +91 -0
  61. package/dist/engine/sync/asana-puller.js.map +1 -0
  62. package/dist/engine/sync/conflict-resolver.d.ts +17 -0
  63. package/dist/engine/sync/conflict-resolver.d.ts.map +1 -0
  64. package/dist/engine/sync/conflict-resolver.js +58 -0
  65. package/dist/engine/sync/conflict-resolver.js.map +1 -0
  66. package/dist/engine/sync/monday-puller.d.ts +9 -0
  67. package/dist/engine/sync/monday-puller.d.ts.map +1 -0
  68. package/dist/engine/sync/monday-puller.js +110 -0
  69. package/dist/engine/sync/monday-puller.js.map +1 -0
  70. package/dist/engine/sync/notion-puller.d.ts +15 -0
  71. package/dist/engine/sync/notion-puller.d.ts.map +1 -0
  72. package/dist/engine/sync/notion-puller.js +101 -0
  73. package/dist/engine/sync/notion-puller.js.map +1 -0
  74. package/dist/engine/verifier/code-scanner.d.ts +8 -0
  75. package/dist/engine/verifier/code-scanner.d.ts.map +1 -0
  76. package/dist/engine/verifier/code-scanner.js +73 -0
  77. package/dist/engine/verifier/code-scanner.js.map +1 -0
  78. package/dist/engine/verifier/compliance-scorer.d.ts +17 -0
  79. package/dist/engine/verifier/compliance-scorer.d.ts.map +1 -0
  80. package/dist/engine/verifier/compliance-scorer.js +131 -0
  81. package/dist/engine/verifier/compliance-scorer.js.map +1 -0
  82. package/dist/engine/verifier/criterion-matcher.d.ts +15 -0
  83. package/dist/engine/verifier/criterion-matcher.d.ts.map +1 -0
  84. package/dist/engine/verifier/criterion-matcher.js +210 -0
  85. package/dist/engine/verifier/criterion-matcher.js.map +1 -0
  86. package/dist/index.js +14 -0
  87. package/dist/index.js.map +1 -1
  88. package/dist/storage/compliance-score-store.d.ts +16 -0
  89. package/dist/storage/compliance-score-store.d.ts.map +1 -0
  90. package/dist/storage/compliance-score-store.js +30 -0
  91. package/dist/storage/compliance-score-store.js.map +1 -0
  92. package/dist/storage/context-profile-store.d.ts +14 -0
  93. package/dist/storage/context-profile-store.d.ts.map +1 -0
  94. package/dist/storage/context-profile-store.js +34 -0
  95. package/dist/storage/context-profile-store.js.map +1 -0
  96. package/dist/storage/workflow-checkpoint-store.d.ts +16 -0
  97. package/dist/storage/workflow-checkpoint-store.d.ts.map +1 -0
  98. package/dist/storage/workflow-checkpoint-store.js +71 -0
  99. package/dist/storage/workflow-checkpoint-store.js.map +1 -0
  100. package/dist/tools/checkpoint/approve-checkpoint-handler.d.ts +3 -0
  101. package/dist/tools/checkpoint/approve-checkpoint-handler.d.ts.map +1 -0
  102. package/dist/tools/checkpoint/approve-checkpoint-handler.js +32 -0
  103. package/dist/tools/checkpoint/approve-checkpoint-handler.js.map +1 -0
  104. package/dist/tools/checkpoint/configure-policy-handler.d.ts +3 -0
  105. package/dist/tools/checkpoint/configure-policy-handler.d.ts.map +1 -0
  106. package/dist/tools/checkpoint/configure-policy-handler.js +60 -0
  107. package/dist/tools/checkpoint/configure-policy-handler.js.map +1 -0
  108. package/dist/tools/checkpoint/list-checkpoints-handler.d.ts +3 -0
  109. package/dist/tools/checkpoint/list-checkpoints-handler.d.ts.map +1 -0
  110. package/dist/tools/checkpoint/list-checkpoints-handler.js +25 -0
  111. package/dist/tools/checkpoint/list-checkpoints-handler.js.map +1 -0
  112. package/dist/tools/checkpoint/reject-checkpoint-handler.d.ts +3 -0
  113. package/dist/tools/checkpoint/reject-checkpoint-handler.d.ts.map +1 -0
  114. package/dist/tools/checkpoint/reject-checkpoint-handler.js +32 -0
  115. package/dist/tools/checkpoint/reject-checkpoint-handler.js.map +1 -0
  116. package/dist/tools/checkpoint/require-checkpoint-handler.d.ts +3 -0
  117. package/dist/tools/checkpoint/require-checkpoint-handler.d.ts.map +1 -0
  118. package/dist/tools/checkpoint/require-checkpoint-handler.js +44 -0
  119. package/dist/tools/checkpoint/require-checkpoint-handler.js.map +1 -0
  120. package/dist/tools/pull-sync-handler.d.ts +25 -0
  121. package/dist/tools/pull-sync-handler.d.ts.map +1 -0
  122. package/dist/tools/pull-sync-handler.js +161 -0
  123. package/dist/tools/pull-sync-handler.js.map +1 -0
  124. package/dist/tools/register-auto-remediation.d.ts +3 -0
  125. package/dist/tools/register-auto-remediation.d.ts.map +1 -0
  126. package/dist/tools/register-auto-remediation.js +174 -0
  127. package/dist/tools/register-auto-remediation.js.map +1 -0
  128. package/dist/tools/register-checkpoints.d.ts +3 -0
  129. package/dist/tools/register-checkpoints.d.ts.map +1 -0
  130. package/dist/tools/register-checkpoints.js +134 -0
  131. package/dist/tools/register-checkpoints.js.map +1 -0
  132. package/dist/tools/register-context-profile.d.ts +3 -0
  133. package/dist/tools/register-context-profile.d.ts.map +1 -0
  134. package/dist/tools/register-context-profile.js +106 -0
  135. package/dist/tools/register-context-profile.js.map +1 -0
  136. package/dist/tools/register-ears.d.ts +3 -0
  137. package/dist/tools/register-ears.d.ts.map +1 -0
  138. package/dist/tools/register-ears.js +148 -0
  139. package/dist/tools/register-ears.js.map +1 -0
  140. package/dist/tools/register-enterprise-compliance.js +1 -1
  141. package/dist/tools/register-enterprise-compliance.js.map +1 -1
  142. package/dist/tools/register-pull-sync.d.ts +3 -0
  143. package/dist/tools/register-pull-sync.d.ts.map +1 -0
  144. package/dist/tools/register-pull-sync.js +71 -0
  145. package/dist/tools/register-pull-sync.js.map +1 -0
  146. package/dist/tools/register-spec405-tools.d.ts +7 -0
  147. package/dist/tools/register-spec405-tools.d.ts.map +1 -0
  148. package/dist/tools/register-spec405-tools.js +194 -0
  149. package/dist/tools/register-spec405-tools.js.map +1 -0
  150. package/dist/tools/register-verifier.d.ts +3 -0
  151. package/dist/tools/register-verifier.d.ts.map +1 -0
  152. package/dist/tools/register-verifier.js +141 -0
  153. package/dist/tools/register-verifier.js.map +1 -0
  154. package/dist/types/analysis.d.ts +98 -0
  155. package/dist/types/analysis.d.ts.map +1 -1
  156. package/dist/types/context-profile.d.ts +22 -0
  157. package/dist/types/context-profile.d.ts.map +1 -0
  158. package/dist/types/context-profile.js +2 -0
  159. package/dist/types/context-profile.js.map +1 -0
  160. package/dist/types/ears.d.ts +34 -0
  161. package/dist/types/ears.d.ts.map +1 -0
  162. package/dist/types/ears.js +3 -0
  163. package/dist/types/ears.js.map +1 -0
  164. package/dist/types/health.d.ts +40 -0
  165. package/dist/types/health.d.ts.map +1 -0
  166. package/dist/types/health.js +3 -0
  167. package/dist/types/health.js.map +1 -0
  168. package/dist/types/index.d.ts +4 -0
  169. package/dist/types/index.d.ts.map +1 -1
  170. package/dist/types/index.js +4 -0
  171. package/dist/types/index.js.map +1 -1
  172. package/dist/types/notion-asana-monday.d.ts +38 -0
  173. package/dist/types/notion-asana-monday.d.ts.map +1 -1
  174. package/dist/types/workflow-checkpoint.d.ts +66 -0
  175. package/dist/types/workflow-checkpoint.d.ts.map +1 -0
  176. package/dist/types/workflow-checkpoint.js +4 -0
  177. package/dist/types/workflow-checkpoint.js.map +1 -0
  178. package/package.json +1 -1
  179. package/src/config/license-plans.json +27 -2
@@ -0,0 +1,144 @@
1
+ // engine/similar-problems/similarity-finder.ts — SPEC-405
2
+ // Find specs similar to a given description across all registered projects.
3
+ import { readFile } from 'node:fs/promises';
4
+ import { join } from 'node:path';
5
+ import { glob } from 'glob';
6
+ import { getProjects } from '../../storage/global-projects-store.js';
7
+ import { parseFrontmatter } from '../frontmatter-parser.js';
8
+ // ---------------------------------------------------------------------------
9
+ // Tokenization + Jaccard similarity
10
+ // ---------------------------------------------------------------------------
11
+ const STOP_WORDS = new Set([
12
+ 'a',
13
+ 'an',
14
+ 'the',
15
+ 'and',
16
+ 'or',
17
+ 'but',
18
+ 'in',
19
+ 'on',
20
+ 'at',
21
+ 'to',
22
+ 'for',
23
+ 'of',
24
+ 'with',
25
+ 'by',
26
+ 'from',
27
+ 'is',
28
+ 'are',
29
+ 'was',
30
+ 'were',
31
+ 'be',
32
+ 'been',
33
+ 'as',
34
+ 'that',
35
+ 'this',
36
+ 'it',
37
+ 'its',
38
+ 'we',
39
+ 'i',
40
+ 'you',
41
+ 'he',
42
+ 'she',
43
+ 'they',
44
+ ]);
45
+ function tokenize(text) {
46
+ return text
47
+ .toLowerCase()
48
+ .replace(/[^a-z0-9\s]/g, ' ')
49
+ .split(/\s+/)
50
+ .filter((w) => w.length > 2 && !STOP_WORDS.has(w));
51
+ }
52
+ function jaccardSimilarity(a, b) {
53
+ const setA = new Set(a);
54
+ const setB = new Set(b);
55
+ let intersection = 0;
56
+ for (const word of setA) {
57
+ if (setB.has(word)) {
58
+ intersection++;
59
+ }
60
+ }
61
+ const union = setA.size + setB.size - intersection;
62
+ return union === 0 ? 0 : intersection / union;
63
+ }
64
+ // ---------------------------------------------------------------------------
65
+ // Spec loading from a project path
66
+ // ---------------------------------------------------------------------------
67
+ async function loadSpecsFromProject(projectPath) {
68
+ let files;
69
+ try {
70
+ const pattern = join(projectPath, 'planu', 'specs', '*', 'spec.md');
71
+ files = await glob(pattern, { nodir: true });
72
+ }
73
+ catch {
74
+ return [];
75
+ }
76
+ const entries = [];
77
+ await Promise.all(files.map(async (filePath) => {
78
+ try {
79
+ const content = await readFile(filePath, 'utf-8');
80
+ const { metadata } = parseFrontmatter(content);
81
+ const specId = typeof metadata.id === 'string' ? metadata.id : '';
82
+ const title = typeof metadata.title === 'string' ? metadata.title : '';
83
+ if (!specId || !title) {
84
+ return;
85
+ }
86
+ const rawTags = metadata.tags;
87
+ const tags = Array.isArray(rawTags)
88
+ ? rawTags.filter((t) => typeof t === 'string')
89
+ : [];
90
+ const tokens = tokenize(`${title} ${tags.join(' ')}`);
91
+ entries.push({ specId, title, tags, projectPath, tokens });
92
+ }
93
+ catch {
94
+ // skip unreadable files
95
+ }
96
+ }));
97
+ return entries;
98
+ }
99
+ // ---------------------------------------------------------------------------
100
+ // Public function
101
+ // ---------------------------------------------------------------------------
102
+ export async function findSimilarProblems(query, projectPath, specId) {
103
+ // Gather registered projects plus the current one
104
+ const registered = await getProjects();
105
+ const paths = new Set([projectPath, ...registered.map((p) => p.path)]);
106
+ // Load all specs across all projects
107
+ const allSpecs = [];
108
+ await Promise.all([...paths].map(async (p) => {
109
+ const specs = await loadSpecsFromProject(p);
110
+ allSpecs.push(...specs);
111
+ }));
112
+ // Build query tokens
113
+ const queryTokens = tokenize(specId ? `${specId} ${query}` : query);
114
+ const scored = [];
115
+ for (const entry of allSpecs) {
116
+ // Skip exact match by specId
117
+ if (specId && entry.specId === specId && entry.projectPath === projectPath) {
118
+ continue;
119
+ }
120
+ const score = jaccardSimilarity(queryTokens, entry.tokens);
121
+ if (score === 0) {
122
+ continue;
123
+ }
124
+ const keyMatches = queryTokens.filter((t) => entry.tokens.includes(t));
125
+ scored.push({
126
+ specId: entry.specId,
127
+ title: entry.title,
128
+ projectPath: entry.projectPath,
129
+ similarityScore: score,
130
+ keyMatches,
131
+ _score: score,
132
+ });
133
+ }
134
+ const results = scored
135
+ .sort((a, b) => b._score - a._score)
136
+ .slice(0, 5)
137
+ .map(({ _score: _, ...rest }) => rest);
138
+ return {
139
+ query,
140
+ results,
141
+ totalSearched: allSpecs.length,
142
+ };
143
+ }
144
+ //# sourceMappingURL=similarity-finder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"similarity-finder.js","sourceRoot":"","sources":["../../../src/engine/similar-problems/similarity-finder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { PmPullSyncResult, PmConflictStrategy } from '../../types/index.js';
2
+ /**
3
+ * Pull updates from Asana into Planu.
4
+ *
5
+ * Compares current Planu spec status against Asana task completion state,
6
+ * detecting divergence and resolving conflicts per the chosen strategy.
7
+ */
8
+ export declare function pullFromAsana(projectPath: string, conflictStrategy: PmConflictStrategy): Promise<PmPullSyncResult>;
9
+ //# sourceMappingURL=asana-puller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asana-puller.d.ts","sourceRoot":"","sources":["../../../src/engine/sync/asana-puller.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,EAEhB,kBAAkB,EAEnB,MAAM,sBAAsB,CAAC;AAM9B;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,WAAW,EAAE,MAAM,EACnB,gBAAgB,EAAE,kBAAkB,GACnC,OAAO,CAAC,gBAAgB,CAAC,CA8D3B"}
@@ -0,0 +1,91 @@
1
+ import { getAsanaConfig, getAsanaSyncMap } from '../../storage/asana-config-store.js';
2
+ import { getSpec } from '../../storage/spec-store.js';
3
+ import { hashProjectPath } from '../../storage/base-store.js';
4
+ import { resolveConflicts } from './conflict-resolver.js';
5
+ /**
6
+ * Pull updates from Asana into Planu.
7
+ *
8
+ * Compares current Planu spec status against Asana task completion state,
9
+ * detecting divergence and resolving conflicts per the chosen strategy.
10
+ */
11
+ export async function pullFromAsana(projectPath, conflictStrategy) {
12
+ const config = await getAsanaConfig(projectPath);
13
+ if (config === null) {
14
+ throw new Error('Asana not configured. Run configure_asana first.');
15
+ }
16
+ const projectId = hashProjectPath(projectPath);
17
+ const syncMap = await getAsanaSyncMap(projectPath);
18
+ const entries = Object.values(syncMap);
19
+ const pulled = [];
20
+ const rawConflicts = [];
21
+ const now = new Date().toISOString();
22
+ for (const entry of entries) {
23
+ const spec = await getSpec(projectId, entry.specId);
24
+ if (spec === null) {
25
+ continue;
26
+ }
27
+ const liveStatus = await fetchAsanaTaskStatus(config.token, entry.taskGid);
28
+ if (liveStatus === null) {
29
+ continue;
30
+ }
31
+ if (liveStatus === spec.status) {
32
+ continue;
33
+ }
34
+ rawConflicts.push({
35
+ specId: spec.id,
36
+ field: 'status',
37
+ planuValue: spec.status,
38
+ externalValue: liveStatus,
39
+ strategy: conflictStrategy,
40
+ });
41
+ }
42
+ const conflicts = resolveConflicts(rawConflicts, conflictStrategy);
43
+ for (const conflict of conflicts) {
44
+ if (conflict.resolution === 'external-applied') {
45
+ pulled.push({
46
+ externalId: syncMap[conflict.specId]?.taskGid ?? '',
47
+ externalTitle: conflict.specId,
48
+ specId: conflict.specId,
49
+ field: conflict.field,
50
+ oldValue: conflict.planuValue,
51
+ newValue: conflict.externalValue,
52
+ appliedAt: now,
53
+ });
54
+ }
55
+ }
56
+ return {
57
+ integration: 'asana',
58
+ pulled,
59
+ conflicts,
60
+ newSpecsCreated: [],
61
+ executedAt: now,
62
+ };
63
+ }
64
+ /**
65
+ * Fetch the current status of an Asana task via the Asana API.
66
+ * Returns a Planu-normalized status string or null on failure.
67
+ */
68
+ async function fetchAsanaTaskStatus(token, taskGid) {
69
+ try {
70
+ const response = await fetch(`https://app.asana.com/api/1.0/tasks/${taskGid}?opt_fields=completed,name`, {
71
+ headers: {
72
+ Authorization: `Bearer ${token}`,
73
+ Accept: 'application/json',
74
+ },
75
+ });
76
+ if (!response.ok) {
77
+ return null;
78
+ }
79
+ const data = (await response.json());
80
+ const completed = data.data?.completed;
81
+ if (completed === undefined) {
82
+ return null;
83
+ }
84
+ // Map Asana task completion to Planu status
85
+ return completed ? 'done' : 'in-progress';
86
+ }
87
+ catch {
88
+ return null;
89
+ }
90
+ }
91
+ //# sourceMappingURL=asana-puller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asana-puller.js","sourceRoot":"","sources":["../../../src/engine/sync/asana-puller.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { PmSyncConflict, PmConflictStrategy } from '../../types/index.js';
2
+ /**
3
+ * Resolve a single conflict using the given strategy.
4
+ *
5
+ * Strategies:
6
+ * - spec-wins → Planu value is authoritative; external change is discarded.
7
+ * - external-wins → External PM value is authoritative; applied to Planu.
8
+ * - newest-wins → The most recently updated side wins. Falls back to spec-wins
9
+ * when timestamps are equal or unparseable.
10
+ * - manual → Conflict is flagged for human review; no automatic resolution.
11
+ */
12
+ export declare function resolveConflict(conflict: Omit<PmSyncConflict, 'resolution'>, strategy: PmConflictStrategy): PmSyncConflict;
13
+ /**
14
+ * Resolve a list of conflicts using the given strategy.
15
+ */
16
+ export declare function resolveConflicts(conflicts: Omit<PmSyncConflict, 'resolution'>[], strategy: PmConflictStrategy): PmSyncConflict[];
17
+ //# sourceMappingURL=conflict-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conflict-resolver.d.ts","sourceRoot":"","sources":["../../../src/engine/sync/conflict-resolver.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE/E;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,IAAI,CAAC,cAAc,EAAE,YAAY,CAAC,EAC5C,QAAQ,EAAE,kBAAkB,GAC3B,cAAc,CAGhB;AAqDD;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,IAAI,CAAC,cAAc,EAAE,YAAY,CAAC,EAAE,EAC/C,QAAQ,EAAE,kBAAkB,GAC3B,cAAc,EAAE,CAElB"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Resolve a single conflict using the given strategy.
3
+ *
4
+ * Strategies:
5
+ * - spec-wins → Planu value is authoritative; external change is discarded.
6
+ * - external-wins → External PM value is authoritative; applied to Planu.
7
+ * - newest-wins → The most recently updated side wins. Falls back to spec-wins
8
+ * when timestamps are equal or unparseable.
9
+ * - manual → Conflict is flagged for human review; no automatic resolution.
10
+ */
11
+ export function resolveConflict(conflict, strategy) {
12
+ const resolution = determineResolution(conflict, strategy);
13
+ return { ...conflict, resolution };
14
+ }
15
+ function determineResolution(conflict, strategy) {
16
+ switch (strategy) {
17
+ case 'spec-wins':
18
+ return 'planu-kept';
19
+ case 'external-wins':
20
+ return 'external-applied';
21
+ case 'newest-wins':
22
+ return resolveByTimestamp(conflict.planuValue, conflict.externalValue);
23
+ case 'manual':
24
+ return 'pending-manual';
25
+ }
26
+ }
27
+ /**
28
+ * Parse ISO timestamp from a value string of the form "status|2024-01-01T00:00:00.000Z".
29
+ * If the value does not contain a timestamp suffix, returns null.
30
+ */
31
+ function extractTimestamp(value) {
32
+ const idx = value.lastIndexOf('|');
33
+ if (idx === -1) {
34
+ return null;
35
+ }
36
+ const ts = value.slice(idx + 1);
37
+ const d = new Date(ts);
38
+ return Number.isNaN(d.getTime()) ? null : d;
39
+ }
40
+ function resolveByTimestamp(planuValue, externalValue) {
41
+ const planuTs = extractTimestamp(planuValue);
42
+ const externalTs = extractTimestamp(externalValue);
43
+ if (planuTs === null || externalTs === null) {
44
+ // Cannot compare — default to spec-wins for safety
45
+ return 'planu-kept';
46
+ }
47
+ if (externalTs > planuTs) {
48
+ return 'external-applied';
49
+ }
50
+ return 'planu-kept';
51
+ }
52
+ /**
53
+ * Resolve a list of conflicts using the given strategy.
54
+ */
55
+ export function resolveConflicts(conflicts, strategy) {
56
+ return conflicts.map((c) => resolveConflict(c, strategy));
57
+ }
58
+ //# sourceMappingURL=conflict-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conflict-resolver.js","sourceRoot":"","sources":["../../../src/engine/sync/conflict-resolver.ts"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAC7B,QAA4C,EAC5C,QAA4B;IAE5B,MAAM,UAAU,GAAG,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC3D,OAAO,EAAE,GAAG,QAAQ,EAAE,UAAU,EAAE,CAAC;AACrC,CAAC;AAED,SAAS,mBAAmB,CAC1B,QAA4C,EAC5C,QAA4B;IAE5B,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,WAAW;YACd,OAAO,YAAY,CAAC;QAEtB,KAAK,eAAe;YAClB,OAAO,kBAAkB,CAAC;QAE5B,KAAK,aAAa;YAChB,OAAO,kBAAkB,CAAC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,aAAa,CAAC,CAAC;QAEzE,KAAK,QAAQ;YACX,OAAO,gBAAgB,CAAC;IAC5B,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,gBAAgB,CAAC,KAAa;IACrC,MAAM,GAAG,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC;QACf,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IAChC,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC;IACvB,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED,SAAS,kBAAkB,CACzB,UAAkB,EAClB,aAAqB;IAErB,MAAM,OAAO,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAC7C,MAAM,UAAU,GAAG,gBAAgB,CAAC,aAAa,CAAC,CAAC;IAEnD,IAAI,OAAO,KAAK,IAAI,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;QAC5C,mDAAmD;QACnD,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,IAAI,UAAU,GAAG,OAAO,EAAE,CAAC;QACzB,OAAO,kBAAkB,CAAC;IAC5B,CAAC;IACD,OAAO,YAAY,CAAC;AACtB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAC9B,SAA+C,EAC/C,QAA4B;IAE5B,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { PmPullSyncResult, PmConflictStrategy } from '../../types/index.js';
2
+ /**
3
+ * Pull updates from Monday.com into Planu.
4
+ *
5
+ * Compares current Planu spec status against Monday.com item column values,
6
+ * detecting divergence and resolving conflicts per the chosen strategy.
7
+ */
8
+ export declare function pullFromMonday(projectPath: string, conflictStrategy: PmConflictStrategy): Promise<PmPullSyncResult>;
9
+ //# sourceMappingURL=monday-puller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"monday-puller.d.ts","sourceRoot":"","sources":["../../../src/engine/sync/monday-puller.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,EAEhB,kBAAkB,EAEnB,MAAM,sBAAsB,CAAC;AAM9B;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,WAAW,EAAE,MAAM,EACnB,gBAAgB,EAAE,kBAAkB,GACnC,OAAO,CAAC,gBAAgB,CAAC,CAgE3B"}
@@ -0,0 +1,110 @@
1
+ import { getMondayConfig, getMondaySyncMap } from '../../storage/monday-config-store.js';
2
+ import { getSpec } from '../../storage/spec-store.js';
3
+ import { hashProjectPath } from '../../storage/base-store.js';
4
+ import { resolveConflicts } from './conflict-resolver.js';
5
+ /**
6
+ * Pull updates from Monday.com into Planu.
7
+ *
8
+ * Compares current Planu spec status against Monday.com item column values,
9
+ * detecting divergence and resolving conflicts per the chosen strategy.
10
+ */
11
+ export async function pullFromMonday(projectPath, conflictStrategy) {
12
+ const config = await getMondayConfig(projectPath);
13
+ if (config === null) {
14
+ throw new Error('Monday.com not configured. Run configure_monday first.');
15
+ }
16
+ const projectId = hashProjectPath(projectPath);
17
+ const syncMap = await getMondaySyncMap(projectPath);
18
+ const entries = Object.values(syncMap);
19
+ const pulled = [];
20
+ const rawConflicts = [];
21
+ const now = new Date().toISOString();
22
+ for (const entry of entries) {
23
+ const spec = await getSpec(projectId, entry.specId);
24
+ if (spec === null) {
25
+ continue;
26
+ }
27
+ // eslint-disable-next-line @typescript-eslint/dot-notation
28
+ const columnId = config.columnMapping?.['status'] ?? 'status';
29
+ const liveStatus = await fetchMondayItemStatus(config.apiKey, entry.itemId, columnId);
30
+ if (liveStatus === null) {
31
+ continue;
32
+ }
33
+ if (liveStatus === spec.status) {
34
+ continue;
35
+ }
36
+ rawConflicts.push({
37
+ specId: spec.id,
38
+ field: 'status',
39
+ planuValue: spec.status,
40
+ externalValue: liveStatus,
41
+ strategy: conflictStrategy,
42
+ });
43
+ }
44
+ const conflicts = resolveConflicts(rawConflicts, conflictStrategy);
45
+ for (const conflict of conflicts) {
46
+ if (conflict.resolution === 'external-applied') {
47
+ pulled.push({
48
+ externalId: syncMap[conflict.specId]?.itemId ?? '',
49
+ externalTitle: conflict.specId,
50
+ specId: conflict.specId,
51
+ field: conflict.field,
52
+ oldValue: conflict.planuValue,
53
+ newValue: conflict.externalValue,
54
+ appliedAt: now,
55
+ });
56
+ }
57
+ }
58
+ return {
59
+ integration: 'monday',
60
+ pulled,
61
+ conflicts,
62
+ newSpecsCreated: [],
63
+ executedAt: now,
64
+ };
65
+ }
66
+ /**
67
+ * Fetch the status column value of a Monday.com item via GraphQL API.
68
+ * Returns a normalized string or null on failure.
69
+ */
70
+ async function fetchMondayItemStatus(apiKey, itemId, columnId) {
71
+ const query = `
72
+ query {
73
+ items(ids: [${itemId}]) {
74
+ column_values(ids: ["${columnId}"]) {
75
+ id
76
+ text
77
+ }
78
+ }
79
+ }
80
+ `;
81
+ try {
82
+ const response = await fetch('https://api.monday.com/v2', {
83
+ method: 'POST',
84
+ headers: {
85
+ Authorization: apiKey,
86
+ 'Content-Type': 'application/json',
87
+ },
88
+ body: JSON.stringify({ query }),
89
+ });
90
+ if (!response.ok) {
91
+ return null;
92
+ }
93
+ const data = (await response.json());
94
+ const items = data.data?.items;
95
+ if (items === undefined || items.length === 0) {
96
+ return null;
97
+ }
98
+ const firstItem = items[0];
99
+ if (firstItem === undefined) {
100
+ return null;
101
+ }
102
+ const colValues = firstItem.column_values ?? [];
103
+ const col = colValues.find((cv) => cv.id === columnId);
104
+ return col?.text ?? null;
105
+ }
106
+ catch {
107
+ return null;
108
+ }
109
+ }
110
+ //# sourceMappingURL=monday-puller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"monday-puller.js","sourceRoot":"","sources":["../../../src/engine/sync/monday-puller.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { PmPullSyncResult, PmConflictStrategy } from '../../types/index.js';
2
+ /**
3
+ * Pull updates from Notion into Planu.
4
+ *
5
+ * Implementation note: Planu operates as an MCP server without persistent
6
+ * HTTP credentials at runtime. Actual Notion API reads would require the
7
+ * user's token to be available in the current process. This implementation
8
+ * performs a local-cache diff: it compares the current Planu spec status
9
+ * against the last-synced status stored in the sync map, surfacing any
10
+ * local changes that have not been pushed yet as "conflicts" from the
11
+ * perspective of the external system. A full bidirectional pull (reading
12
+ * live Notion pages) is performed when the token is available via fetch.
13
+ */
14
+ export declare function pullFromNotion(projectPath: string, conflictStrategy: PmConflictStrategy): Promise<PmPullSyncResult>;
15
+ //# sourceMappingURL=notion-puller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notion-puller.d.ts","sourceRoot":"","sources":["../../../src/engine/sync/notion-puller.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,EAEhB,kBAAkB,EAEnB,MAAM,sBAAsB,CAAC;AAM9B;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,CAClC,WAAW,EAAE,MAAM,EACnB,gBAAgB,EAAE,kBAAkB,GACnC,OAAO,CAAC,gBAAgB,CAAC,CAmE3B"}
@@ -0,0 +1,101 @@
1
+ import { getNotionConfig, getNotionSyncMap } from '../../storage/notion-config-store.js';
2
+ import { getSpec } from '../../storage/spec-store.js';
3
+ import { hashProjectPath } from '../../storage/base-store.js';
4
+ import { resolveConflicts } from './conflict-resolver.js';
5
+ /**
6
+ * Pull updates from Notion into Planu.
7
+ *
8
+ * Implementation note: Planu operates as an MCP server without persistent
9
+ * HTTP credentials at runtime. Actual Notion API reads would require the
10
+ * user's token to be available in the current process. This implementation
11
+ * performs a local-cache diff: it compares the current Planu spec status
12
+ * against the last-synced status stored in the sync map, surfacing any
13
+ * local changes that have not been pushed yet as "conflicts" from the
14
+ * perspective of the external system. A full bidirectional pull (reading
15
+ * live Notion pages) is performed when the token is available via fetch.
16
+ */
17
+ export async function pullFromNotion(projectPath, conflictStrategy) {
18
+ const config = await getNotionConfig(projectPath);
19
+ if (config === null) {
20
+ throw new Error('Notion not configured. Run configure_notion first.');
21
+ }
22
+ const projectId = hashProjectPath(projectPath);
23
+ const syncMap = await getNotionSyncMap(projectPath);
24
+ const entries = Object.values(syncMap);
25
+ const pulled = [];
26
+ const rawConflicts = [];
27
+ const now = new Date().toISOString();
28
+ for (const entry of entries) {
29
+ const spec = await getSpec(projectId, entry.specId);
30
+ if (spec === null) {
31
+ continue;
32
+ }
33
+ // Attempt live fetch from Notion API
34
+ const liveStatus = await fetchNotionPageStatus(config.token, entry.pageId);
35
+ if (liveStatus === null) {
36
+ // API unavailable or page not found — skip this entry
37
+ continue;
38
+ }
39
+ if (liveStatus === spec.status) {
40
+ // No divergence
41
+ continue;
42
+ }
43
+ // Divergence detected: spec status differs from Notion page status
44
+ rawConflicts.push({
45
+ specId: spec.id,
46
+ field: 'status',
47
+ planuValue: spec.status,
48
+ externalValue: liveStatus,
49
+ strategy: conflictStrategy,
50
+ });
51
+ }
52
+ const conflicts = resolveConflicts(rawConflicts, conflictStrategy);
53
+ // Record applied updates
54
+ for (const conflict of conflicts) {
55
+ if (conflict.resolution === 'external-applied') {
56
+ pulled.push({
57
+ externalId: syncMap[conflict.specId]?.pageId ?? '',
58
+ externalTitle: conflict.specId,
59
+ specId: conflict.specId,
60
+ field: conflict.field,
61
+ oldValue: conflict.planuValue,
62
+ newValue: conflict.externalValue,
63
+ appliedAt: now,
64
+ });
65
+ }
66
+ }
67
+ return {
68
+ integration: 'notion',
69
+ pulled,
70
+ conflicts,
71
+ newSpecsCreated: [],
72
+ executedAt: now,
73
+ };
74
+ }
75
+ /**
76
+ * Fetch the current status of a Notion page via the Notion API.
77
+ * Returns null if the request fails (token missing, network error, etc.)
78
+ */
79
+ async function fetchNotionPageStatus(token, pageId) {
80
+ try {
81
+ const response = await fetch(`https://api.notion.com/v1/pages/${pageId}`, {
82
+ headers: {
83
+ Authorization: `Bearer ${token}`,
84
+ 'Notion-Version': '2022-06-28',
85
+ },
86
+ });
87
+ if (!response.ok) {
88
+ return null;
89
+ }
90
+ const data = (await response.json());
91
+ // Try common status property names
92
+ const props = data.properties ?? {};
93
+ // eslint-disable-next-line @typescript-eslint/dot-notation
94
+ const statusProp = props['status'] ?? props['Status'] ?? props['select'] ?? props['Select'];
95
+ return statusProp?.select?.name ?? null;
96
+ }
97
+ catch {
98
+ return null;
99
+ }
100
+ }
101
+ //# sourceMappingURL=notion-puller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notion-puller.js","sourceRoot":"","sources":["../../../src/engine/sync/notion-puller.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { VerifierScanResult } from '../../types/index.js';
2
+ export type { VerifierScanResult };
3
+ /**
4
+ * Scans the codebase for evidence that a spec has been implemented.
5
+ * Searches source files, git commits, and test files.
6
+ */
7
+ export declare function scanForSpecEvidence(specId: string, projectPath: string): Promise<VerifierScanResult>;
8
+ //# sourceMappingURL=code-scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-scanner.d.ts","sourceRoot":"","sources":["../../../src/engine/verifier/code-scanner.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE/D,YAAY,EAAE,kBAAkB,EAAE,CAAC;AAsCnC;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,kBAAkB,CAAC,CAwC7B"}