@planu/cli 1.10.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.
- package/dist/config/license-plans.json +32 -2
- package/dist/engine/api-compat/compatibility-checker.d.ts +4 -0
- package/dist/engine/api-compat/compatibility-checker.d.ts.map +1 -0
- package/dist/engine/api-compat/compatibility-checker.js +118 -0
- package/dist/engine/api-compat/compatibility-checker.js.map +1 -0
- package/dist/engine/checkpoint/checkpoint-manager.d.ts +22 -0
- package/dist/engine/checkpoint/checkpoint-manager.d.ts.map +1 -0
- package/dist/engine/checkpoint/checkpoint-manager.js +76 -0
- package/dist/engine/checkpoint/checkpoint-manager.js.map +1 -0
- package/dist/engine/checkpoint/policy-engine.d.ts +10 -0
- package/dist/engine/checkpoint/policy-engine.d.ts.map +1 -0
- package/dist/engine/checkpoint/policy-engine.js +87 -0
- package/dist/engine/checkpoint/policy-engine.js.map +1 -0
- package/dist/engine/compliance/auto-remediator.d.ts +9 -0
- package/dist/engine/compliance/auto-remediator.d.ts.map +1 -0
- package/dist/engine/compliance/auto-remediator.js +118 -0
- package/dist/engine/compliance/auto-remediator.js.map +1 -0
- package/dist/engine/compliance/evidence-builder.d.ts +19 -0
- package/dist/engine/compliance/evidence-builder.d.ts.map +1 -0
- package/dist/engine/compliance/evidence-builder.js +36 -0
- package/dist/engine/compliance/evidence-builder.js.map +1 -0
- package/dist/engine/compliance/framework-catalog.d.ts +15 -0
- package/dist/engine/compliance/framework-catalog.d.ts.map +1 -0
- package/dist/engine/compliance/framework-catalog.js +134 -0
- package/dist/engine/compliance/framework-catalog.js.map +1 -0
- package/dist/engine/compliance/gap-analyzer.d.ts +11 -0
- package/dist/engine/compliance/gap-analyzer.d.ts.map +1 -0
- package/dist/engine/compliance/gap-analyzer.js +39 -0
- package/dist/engine/compliance/gap-analyzer.js.map +1 -0
- package/dist/engine/context-profile/profile-catalog.d.ts +5 -0
- package/dist/engine/context-profile/profile-catalog.d.ts.map +1 -0
- package/dist/engine/context-profile/profile-catalog.js +145 -0
- package/dist/engine/context-profile/profile-catalog.js.map +1 -0
- package/dist/engine/critical-path/path-analyzer.d.ts +3 -0
- package/dist/engine/critical-path/path-analyzer.d.ts.map +1 -0
- package/dist/engine/critical-path/path-analyzer.js +145 -0
- package/dist/engine/critical-path/path-analyzer.js.map +1 -0
- package/dist/engine/distribution/cost-estimator.d.ts +8 -0
- package/dist/engine/distribution/cost-estimator.d.ts.map +1 -0
- package/dist/engine/distribution/cost-estimator.js +113 -0
- package/dist/engine/distribution/cost-estimator.js.map +1 -0
- package/dist/engine/distribution/diagram-generator.d.ts +12 -0
- package/dist/engine/distribution/diagram-generator.d.ts.map +1 -0
- package/dist/engine/distribution/diagram-generator.js +59 -0
- package/dist/engine/distribution/diagram-generator.js.map +1 -0
- package/dist/engine/distribution/readiness-analyzer.d.ts +4 -0
- package/dist/engine/distribution/readiness-analyzer.d.ts.map +1 -0
- package/dist/engine/distribution/readiness-analyzer.js +61 -0
- package/dist/engine/distribution/readiness-analyzer.js.map +1 -0
- package/dist/engine/distribution/runbook-generator.d.ts +7 -0
- package/dist/engine/distribution/runbook-generator.d.ts.map +1 -0
- package/dist/engine/distribution/runbook-generator.js +42 -0
- package/dist/engine/distribution/runbook-generator.js.map +1 -0
- package/dist/engine/drift/violation-resolver.d.ts +9 -0
- package/dist/engine/drift/violation-resolver.d.ts.map +1 -0
- package/dist/engine/drift/violation-resolver.js +128 -0
- package/dist/engine/drift/violation-resolver.js.map +1 -0
- package/dist/engine/ears/criterion-scorer.d.ts +7 -0
- package/dist/engine/ears/criterion-scorer.d.ts.map +1 -0
- package/dist/engine/ears/criterion-scorer.js +87 -0
- package/dist/engine/ears/criterion-scorer.js.map +1 -0
- package/dist/engine/ears/pattern-matcher.d.ts +5 -0
- package/dist/engine/ears/pattern-matcher.d.ts.map +1 -0
- package/dist/engine/ears/pattern-matcher.js +48 -0
- package/dist/engine/ears/pattern-matcher.js.map +1 -0
- package/dist/engine/ears/rewriter.d.ts +7 -0
- package/dist/engine/ears/rewriter.d.ts.map +1 -0
- package/dist/engine/ears/rewriter.js +45 -0
- package/dist/engine/ears/rewriter.js.map +1 -0
- package/dist/engine/ears/spec-linter.d.ts +7 -0
- package/dist/engine/ears/spec-linter.d.ts.map +1 -0
- package/dist/engine/ears/spec-linter.js +127 -0
- package/dist/engine/ears/spec-linter.js.map +1 -0
- package/dist/engine/health/auto-fixer.d.ts +7 -0
- package/dist/engine/health/auto-fixer.d.ts.map +1 -0
- package/dist/engine/health/auto-fixer.js +130 -0
- package/dist/engine/health/auto-fixer.js.map +1 -0
- package/dist/engine/mcp-catalog/catalog-advisor.d.ts +3 -0
- package/dist/engine/mcp-catalog/catalog-advisor.d.ts.map +1 -0
- package/dist/engine/mcp-catalog/catalog-advisor.js +180 -0
- package/dist/engine/mcp-catalog/catalog-advisor.js.map +1 -0
- package/dist/engine/mcp-hub/adapter-registry.d.ts +23 -0
- package/dist/engine/mcp-hub/adapter-registry.d.ts.map +1 -0
- package/dist/engine/mcp-hub/adapter-registry.js +19 -0
- package/dist/engine/mcp-hub/adapter-registry.js.map +1 -0
- package/dist/engine/mcp-hub/adapters/github-adapter.d.ts +4 -0
- package/dist/engine/mcp-hub/adapters/github-adapter.d.ts.map +1 -0
- package/dist/engine/mcp-hub/adapters/github-adapter.js +44 -0
- package/dist/engine/mcp-hub/adapters/github-adapter.js.map +1 -0
- package/dist/engine/mcp-hub/adapters/supabase-adapter.d.ts +4 -0
- package/dist/engine/mcp-hub/adapters/supabase-adapter.d.ts.map +1 -0
- package/dist/engine/mcp-hub/adapters/supabase-adapter.js +41 -0
- package/dist/engine/mcp-hub/adapters/supabase-adapter.js.map +1 -0
- package/dist/engine/mcp-hub/event-router.d.ts +19 -0
- package/dist/engine/mcp-hub/event-router.d.ts.map +1 -0
- package/dist/engine/mcp-hub/event-router.js +47 -0
- package/dist/engine/mcp-hub/event-router.js.map +1 -0
- package/dist/engine/productivity/calibration-engine.d.ts +17 -0
- package/dist/engine/productivity/calibration-engine.d.ts.map +1 -0
- package/dist/engine/productivity/calibration-engine.js +49 -0
- package/dist/engine/productivity/calibration-engine.js.map +1 -0
- package/dist/engine/productivity/time-tracker.d.ts +6 -0
- package/dist/engine/productivity/time-tracker.d.ts.map +1 -0
- package/dist/engine/productivity/time-tracker.js +28 -0
- package/dist/engine/productivity/time-tracker.js.map +1 -0
- package/dist/engine/productivity/vibe-tax-calculator.d.ts +14 -0
- package/dist/engine/productivity/vibe-tax-calculator.d.ts.map +1 -0
- package/dist/engine/productivity/vibe-tax-calculator.js +63 -0
- package/dist/engine/productivity/vibe-tax-calculator.js.map +1 -0
- package/dist/engine/quality-gates/gate-catalog.d.ts +6 -0
- package/dist/engine/quality-gates/gate-catalog.d.ts.map +1 -0
- package/dist/engine/quality-gates/gate-catalog.js +351 -0
- package/dist/engine/quality-gates/gate-catalog.js.map +1 -0
- package/dist/engine/quality-gates/gate-evaluator.d.ts +3 -0
- package/dist/engine/quality-gates/gate-evaluator.d.ts.map +1 -0
- package/dist/engine/quality-gates/gate-evaluator.js +25 -0
- package/dist/engine/quality-gates/gate-evaluator.js.map +1 -0
- package/dist/engine/quality-gates/gate-injector.d.ts +20 -0
- package/dist/engine/quality-gates/gate-injector.d.ts.map +1 -0
- package/dist/engine/quality-gates/gate-injector.js +75 -0
- package/dist/engine/quality-gates/gate-injector.js.map +1 -0
- package/dist/engine/similar-problems/similarity-finder.d.ts +3 -0
- package/dist/engine/similar-problems/similarity-finder.d.ts.map +1 -0
- package/dist/engine/similar-problems/similarity-finder.js +144 -0
- package/dist/engine/similar-problems/similarity-finder.js.map +1 -0
- package/dist/engine/sync/asana-puller.d.ts +9 -0
- package/dist/engine/sync/asana-puller.d.ts.map +1 -0
- package/dist/engine/sync/asana-puller.js +91 -0
- package/dist/engine/sync/asana-puller.js.map +1 -0
- package/dist/engine/sync/conflict-resolver.d.ts +17 -0
- package/dist/engine/sync/conflict-resolver.d.ts.map +1 -0
- package/dist/engine/sync/conflict-resolver.js +58 -0
- package/dist/engine/sync/conflict-resolver.js.map +1 -0
- package/dist/engine/sync/monday-puller.d.ts +9 -0
- package/dist/engine/sync/monday-puller.d.ts.map +1 -0
- package/dist/engine/sync/monday-puller.js +110 -0
- package/dist/engine/sync/monday-puller.js.map +1 -0
- package/dist/engine/sync/notion-puller.d.ts +15 -0
- package/dist/engine/sync/notion-puller.d.ts.map +1 -0
- package/dist/engine/sync/notion-puller.js +101 -0
- package/dist/engine/sync/notion-puller.js.map +1 -0
- package/dist/engine/verifier/code-scanner.d.ts +8 -0
- package/dist/engine/verifier/code-scanner.d.ts.map +1 -0
- package/dist/engine/verifier/code-scanner.js +73 -0
- package/dist/engine/verifier/code-scanner.js.map +1 -0
- package/dist/engine/verifier/compliance-scorer.d.ts +17 -0
- package/dist/engine/verifier/compliance-scorer.d.ts.map +1 -0
- package/dist/engine/verifier/compliance-scorer.js +131 -0
- package/dist/engine/verifier/compliance-scorer.js.map +1 -0
- package/dist/engine/verifier/criterion-matcher.d.ts +15 -0
- package/dist/engine/verifier/criterion-matcher.d.ts.map +1 -0
- package/dist/engine/verifier/criterion-matcher.js +210 -0
- package/dist/engine/verifier/criterion-matcher.js.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -1
- package/dist/storage/compliance-audit-store.d.ts +12 -0
- package/dist/storage/compliance-audit-store.d.ts.map +1 -0
- package/dist/storage/compliance-audit-store.js +40 -0
- package/dist/storage/compliance-audit-store.js.map +1 -0
- package/dist/storage/compliance-score-store.d.ts +16 -0
- package/dist/storage/compliance-score-store.d.ts.map +1 -0
- package/dist/storage/compliance-score-store.js +30 -0
- package/dist/storage/compliance-score-store.js.map +1 -0
- package/dist/storage/context-profile-store.d.ts +14 -0
- package/dist/storage/context-profile-store.d.ts.map +1 -0
- package/dist/storage/context-profile-store.js +34 -0
- package/dist/storage/context-profile-store.js.map +1 -0
- package/dist/storage/mcp-hub-store.d.ts +9 -0
- package/dist/storage/mcp-hub-store.d.ts.map +1 -0
- package/dist/storage/mcp-hub-store.js +28 -0
- package/dist/storage/mcp-hub-store.js.map +1 -0
- package/dist/storage/time-tracking-store.d.ts +6 -0
- package/dist/storage/time-tracking-store.d.ts.map +1 -0
- package/dist/storage/time-tracking-store.js +34 -0
- package/dist/storage/time-tracking-store.js.map +1 -0
- package/dist/storage/workflow-checkpoint-store.d.ts +16 -0
- package/dist/storage/workflow-checkpoint-store.d.ts.map +1 -0
- package/dist/storage/workflow-checkpoint-store.js +71 -0
- package/dist/storage/workflow-checkpoint-store.js.map +1 -0
- package/dist/tools/checkpoint/approve-checkpoint-handler.d.ts +3 -0
- package/dist/tools/checkpoint/approve-checkpoint-handler.d.ts.map +1 -0
- package/dist/tools/checkpoint/approve-checkpoint-handler.js +32 -0
- package/dist/tools/checkpoint/approve-checkpoint-handler.js.map +1 -0
- package/dist/tools/checkpoint/configure-policy-handler.d.ts +3 -0
- package/dist/tools/checkpoint/configure-policy-handler.d.ts.map +1 -0
- package/dist/tools/checkpoint/configure-policy-handler.js +60 -0
- package/dist/tools/checkpoint/configure-policy-handler.js.map +1 -0
- package/dist/tools/checkpoint/list-checkpoints-handler.d.ts +3 -0
- package/dist/tools/checkpoint/list-checkpoints-handler.d.ts.map +1 -0
- package/dist/tools/checkpoint/list-checkpoints-handler.js +25 -0
- package/dist/tools/checkpoint/list-checkpoints-handler.js.map +1 -0
- package/dist/tools/checkpoint/reject-checkpoint-handler.d.ts +3 -0
- package/dist/tools/checkpoint/reject-checkpoint-handler.d.ts.map +1 -0
- package/dist/tools/checkpoint/reject-checkpoint-handler.js +32 -0
- package/dist/tools/checkpoint/reject-checkpoint-handler.js.map +1 -0
- package/dist/tools/checkpoint/require-checkpoint-handler.d.ts +3 -0
- package/dist/tools/checkpoint/require-checkpoint-handler.d.ts.map +1 -0
- package/dist/tools/checkpoint/require-checkpoint-handler.js +44 -0
- package/dist/tools/checkpoint/require-checkpoint-handler.js.map +1 -0
- package/dist/tools/compliance-gap-handler.d.ts +5 -0
- package/dist/tools/compliance-gap-handler.d.ts.map +1 -0
- package/dist/tools/compliance-gap-handler.js +64 -0
- package/dist/tools/compliance-gap-handler.js.map +1 -0
- package/dist/tools/distribution-readiness-handler.d.ts +3 -0
- package/dist/tools/distribution-readiness-handler.d.ts.map +1 -0
- package/dist/tools/distribution-readiness-handler.js +24 -0
- package/dist/tools/distribution-readiness-handler.js.map +1 -0
- package/dist/tools/generate-cost-estimate-handler.d.ts +3 -0
- package/dist/tools/generate-cost-estimate-handler.d.ts.map +1 -0
- package/dist/tools/generate-cost-estimate-handler.js +29 -0
- package/dist/tools/generate-cost-estimate-handler.js.map +1 -0
- package/dist/tools/generate-deployment-diagram-handler.d.ts +3 -0
- package/dist/tools/generate-deployment-diagram-handler.d.ts.map +1 -0
- package/dist/tools/generate-deployment-diagram-handler.js +26 -0
- package/dist/tools/generate-deployment-diagram-handler.js.map +1 -0
- package/dist/tools/generate-runbook-handler.d.ts +3 -0
- package/dist/tools/generate-runbook-handler.d.ts.map +1 -0
- package/dist/tools/generate-runbook-handler.js +18 -0
- package/dist/tools/generate-runbook-handler.js.map +1 -0
- package/dist/tools/inject-quality-gates-handler.d.ts +7 -0
- package/dist/tools/inject-quality-gates-handler.d.ts.map +1 -0
- package/dist/tools/inject-quality-gates-handler.js +59 -0
- package/dist/tools/inject-quality-gates-handler.js.map +1 -0
- package/dist/tools/mcp-hub-handler.d.ts +7 -0
- package/dist/tools/mcp-hub-handler.d.ts.map +1 -0
- package/dist/tools/mcp-hub-handler.js +73 -0
- package/dist/tools/mcp-hub-handler.js.map +1 -0
- package/dist/tools/productivity-report-handler.d.ts +4 -0
- package/dist/tools/productivity-report-handler.d.ts.map +1 -0
- package/dist/tools/productivity-report-handler.js +98 -0
- package/dist/tools/productivity-report-handler.js.map +1 -0
- package/dist/tools/pull-sync-handler.d.ts +25 -0
- package/dist/tools/pull-sync-handler.d.ts.map +1 -0
- package/dist/tools/pull-sync-handler.js +161 -0
- package/dist/tools/pull-sync-handler.js.map +1 -0
- package/dist/tools/register-auto-remediation.d.ts +3 -0
- package/dist/tools/register-auto-remediation.d.ts.map +1 -0
- package/dist/tools/register-auto-remediation.js +174 -0
- package/dist/tools/register-auto-remediation.js.map +1 -0
- package/dist/tools/register-checkpoints.d.ts +3 -0
- package/dist/tools/register-checkpoints.d.ts.map +1 -0
- package/dist/tools/register-checkpoints.js +134 -0
- package/dist/tools/register-checkpoints.js.map +1 -0
- package/dist/tools/register-context-profile.d.ts +3 -0
- package/dist/tools/register-context-profile.d.ts.map +1 -0
- package/dist/tools/register-context-profile.js +106 -0
- package/dist/tools/register-context-profile.js.map +1 -0
- package/dist/tools/register-distribution.d.ts +3 -0
- package/dist/tools/register-distribution.d.ts.map +1 -0
- package/dist/tools/register-distribution.js +103 -0
- package/dist/tools/register-distribution.js.map +1 -0
- package/dist/tools/register-ears.d.ts +3 -0
- package/dist/tools/register-ears.d.ts.map +1 -0
- package/dist/tools/register-ears.js +148 -0
- package/dist/tools/register-ears.js.map +1 -0
- package/dist/tools/register-enterprise-compliance.d.ts +3 -0
- package/dist/tools/register-enterprise-compliance.d.ts.map +1 -0
- package/dist/tools/register-enterprise-compliance.js +30 -0
- package/dist/tools/register-enterprise-compliance.js.map +1 -0
- package/dist/tools/register-mcp-hub.d.ts +3 -0
- package/dist/tools/register-mcp-hub.d.ts.map +1 -0
- package/dist/tools/register-mcp-hub.js +47 -0
- package/dist/tools/register-mcp-hub.js.map +1 -0
- package/dist/tools/register-productivity.d.ts +3 -0
- package/dist/tools/register-productivity.d.ts.map +1 -0
- package/dist/tools/register-productivity.js +25 -0
- package/dist/tools/register-productivity.js.map +1 -0
- package/dist/tools/register-pull-sync.d.ts +3 -0
- package/dist/tools/register-pull-sync.d.ts.map +1 -0
- package/dist/tools/register-pull-sync.js +71 -0
- package/dist/tools/register-pull-sync.js.map +1 -0
- package/dist/tools/register-quality-gates.d.ts +3 -0
- package/dist/tools/register-quality-gates.d.ts.map +1 -0
- package/dist/tools/register-quality-gates.js +47 -0
- package/dist/tools/register-quality-gates.js.map +1 -0
- package/dist/tools/register-spec405-tools.d.ts +7 -0
- package/dist/tools/register-spec405-tools.d.ts.map +1 -0
- package/dist/tools/register-spec405-tools.js +194 -0
- package/dist/tools/register-spec405-tools.js.map +1 -0
- package/dist/tools/register-verifier.d.ts +3 -0
- package/dist/tools/register-verifier.d.ts.map +1 -0
- package/dist/tools/register-verifier.js +141 -0
- package/dist/tools/register-verifier.js.map +1 -0
- package/dist/types/analysis.d.ts +128 -0
- package/dist/types/analysis.d.ts.map +1 -1
- package/dist/types/context-profile.d.ts +22 -0
- package/dist/types/context-profile.d.ts.map +1 -0
- package/dist/types/context-profile.js +2 -0
- package/dist/types/context-profile.js.map +1 -0
- package/dist/types/docs.d.ts +43 -0
- package/dist/types/docs.d.ts.map +1 -1
- package/dist/types/ears.d.ts +34 -0
- package/dist/types/ears.d.ts.map +1 -0
- package/dist/types/ears.js +3 -0
- package/dist/types/ears.js.map +1 -0
- package/dist/types/env.d.ts +23 -0
- package/dist/types/env.d.ts.map +1 -1
- package/dist/types/estimation.d.ts +30 -0
- package/dist/types/estimation.d.ts.map +1 -1
- package/dist/types/health.d.ts +40 -0
- package/dist/types/health.d.ts.map +1 -0
- package/dist/types/health.js +3 -0
- package/dist/types/health.js.map +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +4 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/notion-asana-monday.d.ts +38 -0
- package/dist/types/notion-asana-monday.d.ts.map +1 -1
- package/dist/types/orchestration/index.d.ts +1 -0
- package/dist/types/orchestration/index.d.ts.map +1 -1
- package/dist/types/orchestration/mcp-hub.d.ts +29 -0
- package/dist/types/orchestration/mcp-hub.d.ts.map +1 -0
- package/dist/types/orchestration/mcp-hub.js +3 -0
- package/dist/types/orchestration/mcp-hub.js.map +1 -0
- package/dist/types/orchestration.d.ts +1 -1
- package/dist/types/orchestration.d.ts.map +1 -1
- package/dist/types/workflow-checkpoint.d.ts +66 -0
- package/dist/types/workflow-checkpoint.d.ts.map +1 -0
- package/dist/types/workflow-checkpoint.js +4 -0
- package/dist/types/workflow-checkpoint.js.map +1 -0
- package/package.json +1 -1
- package/src/config/license-plans.json +32 -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"}
|