specforge-mcp 0.10.0 → 0.11.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 (99) hide show
  1. package/dist/engine/actuals/backfill.d.ts +14 -0
  2. package/dist/engine/actuals/backfill.d.ts.map +1 -0
  3. package/dist/engine/actuals/backfill.js +62 -0
  4. package/dist/engine/actuals/backfill.js.map +1 -0
  5. package/dist/engine/actuals/consolidator.d.ts +12 -0
  6. package/dist/engine/actuals/consolidator.d.ts.map +1 -0
  7. package/dist/engine/actuals/consolidator.js +54 -0
  8. package/dist/engine/actuals/consolidator.js.map +1 -0
  9. package/dist/engine/actuals/git-analyzer.d.ts +23 -0
  10. package/dist/engine/actuals/git-analyzer.d.ts.map +1 -0
  11. package/dist/engine/actuals/git-analyzer.js +169 -0
  12. package/dist/engine/actuals/git-analyzer.js.map +1 -0
  13. package/dist/engine/actuals/index.d.ts +5 -0
  14. package/dist/engine/actuals/index.d.ts.map +1 -0
  15. package/dist/engine/actuals/index.js +7 -0
  16. package/dist/engine/actuals/index.js.map +1 -0
  17. package/dist/engine/actuals/wip-tracker.d.ts +25 -0
  18. package/dist/engine/actuals/wip-tracker.d.ts.map +1 -0
  19. package/dist/engine/actuals/wip-tracker.js +71 -0
  20. package/dist/engine/actuals/wip-tracker.js.map +1 -0
  21. package/dist/engine/doc-generator/executive-summary/data-extractors.d.ts +12 -0
  22. package/dist/engine/doc-generator/executive-summary/data-extractors.d.ts.map +1 -0
  23. package/dist/engine/doc-generator/executive-summary/data-extractors.js +96 -0
  24. package/dist/engine/doc-generator/executive-summary/data-extractors.js.map +1 -0
  25. package/dist/engine/doc-generator/executive-summary/executive-summary-generator.d.ts +7 -0
  26. package/dist/engine/doc-generator/executive-summary/executive-summary-generator.d.ts.map +1 -0
  27. package/dist/engine/doc-generator/executive-summary/executive-summary-generator.js +38 -0
  28. package/dist/engine/doc-generator/executive-summary/executive-summary-generator.js.map +1 -0
  29. package/dist/engine/doc-generator/executive-summary/html-template.d.ts +3 -0
  30. package/dist/engine/doc-generator/executive-summary/html-template.d.ts.map +1 -0
  31. package/dist/engine/doc-generator/executive-summary/html-template.js +53 -0
  32. package/dist/engine/doc-generator/executive-summary/html-template.js.map +1 -0
  33. package/dist/engine/doc-generator/executive-summary/index.d.ts +2 -0
  34. package/dist/engine/doc-generator/executive-summary/index.d.ts.map +1 -0
  35. package/dist/engine/doc-generator/executive-summary/index.js +3 -0
  36. package/dist/engine/doc-generator/executive-summary/index.js.map +1 -0
  37. package/dist/engine/doc-generator/executive-summary/section-builders.d.ts +18 -0
  38. package/dist/engine/doc-generator/executive-summary/section-builders.d.ts.map +1 -0
  39. package/dist/engine/doc-generator/executive-summary/section-builders.js +152 -0
  40. package/dist/engine/doc-generator/executive-summary/section-builders.js.map +1 -0
  41. package/dist/engine/doc-generator/executive-summary/svg-diagrams.d.ts +8 -0
  42. package/dist/engine/doc-generator/executive-summary/svg-diagrams.d.ts.map +1 -0
  43. package/dist/engine/doc-generator/executive-summary/svg-diagrams.js +124 -0
  44. package/dist/engine/doc-generator/executive-summary/svg-diagrams.js.map +1 -0
  45. package/dist/engine/doc-generator.d.ts.map +1 -1
  46. package/dist/engine/doc-generator.js +2 -0
  47. package/dist/engine/doc-generator.js.map +1 -1
  48. package/dist/engine/risk-analyzer/approach-analyzer.d.ts +6 -0
  49. package/dist/engine/risk-analyzer/approach-analyzer.d.ts.map +1 -0
  50. package/dist/engine/risk-analyzer/approach-analyzer.js +86 -0
  51. package/dist/engine/risk-analyzer/approach-analyzer.js.map +1 -0
  52. package/dist/engine/risk-analyzer/complexity-calculator.d.ts +6 -0
  53. package/dist/engine/risk-analyzer/complexity-calculator.d.ts.map +1 -0
  54. package/dist/engine/risk-analyzer/complexity-calculator.js +110 -0
  55. package/dist/engine/risk-analyzer/complexity-calculator.js.map +1 -0
  56. package/dist/engine/risk-analyzer/confidence-scorer.d.ts +7 -0
  57. package/dist/engine/risk-analyzer/confidence-scorer.d.ts.map +1 -0
  58. package/dist/engine/risk-analyzer/confidence-scorer.js +113 -0
  59. package/dist/engine/risk-analyzer/confidence-scorer.js.map +1 -0
  60. package/dist/engine/risk-analyzer/index.d.ts +6 -0
  61. package/dist/engine/risk-analyzer/index.d.ts.map +1 -0
  62. package/dist/engine/risk-analyzer/index.js +7 -0
  63. package/dist/engine/risk-analyzer/index.js.map +1 -0
  64. package/dist/engine/risk-analyzer/risk-generator.d.ts +6 -0
  65. package/dist/engine/risk-analyzer/risk-generator.d.ts.map +1 -0
  66. package/dist/engine/risk-analyzer/risk-generator.js +94 -0
  67. package/dist/engine/risk-analyzer/risk-generator.js.map +1 -0
  68. package/dist/engine/risk-analyzer/tradeoff-detector.d.ts +6 -0
  69. package/dist/engine/risk-analyzer/tradeoff-detector.d.ts.map +1 -0
  70. package/dist/engine/risk-analyzer/tradeoff-detector.js +128 -0
  71. package/dist/engine/risk-analyzer/tradeoff-detector.js.map +1 -0
  72. package/dist/storage/risk-store.d.ts +16 -0
  73. package/dist/storage/risk-store.d.ts.map +1 -0
  74. package/dist/storage/risk-store.js +53 -0
  75. package/dist/storage/risk-store.js.map +1 -0
  76. package/dist/tools/schemas/lifecycle.d.ts +2 -0
  77. package/dist/tools/schemas/lifecycle.d.ts.map +1 -1
  78. package/dist/tools/schemas/lifecycle.js +3 -1
  79. package/dist/tools/schemas/lifecycle.js.map +1 -1
  80. package/dist/types/actuals-tracking.d.ts +51 -0
  81. package/dist/types/actuals-tracking.d.ts.map +1 -0
  82. package/dist/types/actuals-tracking.js +2 -0
  83. package/dist/types/actuals-tracking.js.map +1 -0
  84. package/dist/types/common/tech-enums.d.ts +1 -1
  85. package/dist/types/common/tech-enums.d.ts.map +1 -1
  86. package/dist/types/docs.d.ts +44 -1
  87. package/dist/types/docs.d.ts.map +1 -1
  88. package/dist/types/index.d.ts +2 -0
  89. package/dist/types/index.d.ts.map +1 -1
  90. package/dist/types/index.js +2 -0
  91. package/dist/types/index.js.map +1 -1
  92. package/dist/types/risk.d.ts +90 -0
  93. package/dist/types/risk.d.ts.map +1 -0
  94. package/dist/types/risk.js +2 -0
  95. package/dist/types/risk.js.map +1 -0
  96. package/package.json +1 -1
  97. package/src/i18n/messages/en.json +1 -1
  98. package/src/i18n/messages/es.json +1 -1
  99. package/src/i18n/messages/pt.json +1 -1
@@ -0,0 +1,14 @@
1
+ import type { Spec } from '../../types/spec/core.js';
2
+ import type { BackfillReport, ConsolidatedActuals } from '../../types/index.js';
3
+ /**
4
+ * Backfills actuals for specs that have no actuals yet, by deriving from git history.
5
+ *
6
+ * This function does NOT persist to spec-store — it returns the report and derived data.
7
+ * The caller decides whether to persist.
8
+ */
9
+ export declare function backfillActualsFromGit(projectPath: string, _projectDataDir: string, specs: Spec[]): Promise<BackfillReport>;
10
+ /**
11
+ * Derives actuals for a single spec and returns a ConsolidatedActuals or null.
12
+ */
13
+ export declare function deriveAndConsolidateSingle(projectPath: string, spec: Spec): Promise<ConsolidatedActuals | null>;
14
+ //# sourceMappingURL=backfill.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backfill.d.ts","sourceRoot":"","sources":["../../../src/engine/actuals/backfill.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAkB,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAMhG;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,WAAW,EAAE,MAAM,EACnB,eAAe,EAAE,MAAM,EACvB,KAAK,EAAE,IAAI,EAAE,GACZ,OAAO,CAAC,cAAc,CAAC,CAkDzB;AAED;;GAEG;AACH,wBAAsB,0BAA0B,CAC9C,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,IAAI,GACT,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAMrC"}
@@ -0,0 +1,62 @@
1
+ import { deriveActualsFromGit } from './git-analyzer.js';
2
+ import { consolidateActuals } from './consolidator.js';
3
+ const ELIGIBLE_STATUSES = new Set(['implementing', 'done']);
4
+ /**
5
+ * Backfills actuals for specs that have no actuals yet, by deriving from git history.
6
+ *
7
+ * This function does NOT persist to spec-store — it returns the report and derived data.
8
+ * The caller decides whether to persist.
9
+ */
10
+ export async function backfillActualsFromGit(projectPath, _projectDataDir, specs) {
11
+ const report = {
12
+ totalSpecs: specs.length,
13
+ populated: 0,
14
+ skipped: 0,
15
+ noGitData: 0,
16
+ alreadyHadActuals: 0,
17
+ totalDerivedHours: 0,
18
+ results: [],
19
+ };
20
+ for (const spec of specs) {
21
+ if (!ELIGIBLE_STATUSES.has(spec.status)) {
22
+ report.skipped++;
23
+ continue;
24
+ }
25
+ if (spec.actuals !== null) {
26
+ report.alreadyHadActuals++;
27
+ continue;
28
+ }
29
+ const gitDerived = await deriveActualsFromGit(projectPath, spec.id);
30
+ if (!gitDerived) {
31
+ report.noGitData++;
32
+ continue;
33
+ }
34
+ const hourlyRate = spec.estimation.hourlyRate;
35
+ const consolidated = consolidateActuals({ hourlyRate }, null, null, gitDerived);
36
+ /* v8 ignore start */
37
+ if (!consolidated) {
38
+ report.noGitData++;
39
+ continue;
40
+ }
41
+ /* v8 ignore stop */
42
+ report.populated++;
43
+ report.totalDerivedHours += gitDerived.devHours;
44
+ const result = {
45
+ specId: spec.id,
46
+ actuals: consolidated,
47
+ };
48
+ report.results.push(result);
49
+ }
50
+ return report;
51
+ }
52
+ /**
53
+ * Derives actuals for a single spec and returns a ConsolidatedActuals or null.
54
+ */
55
+ export async function deriveAndConsolidateSingle(projectPath, spec) {
56
+ const gitDerived = await deriveActualsFromGit(projectPath, spec.id);
57
+ if (!gitDerived) {
58
+ return null;
59
+ }
60
+ return consolidateActuals({ hourlyRate: spec.estimation.hourlyRate }, null, null, gitDerived);
61
+ }
62
+ //# sourceMappingURL=backfill.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backfill.js","sourceRoot":"","sources":["../../../src/engine/actuals/backfill.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEvD,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAS,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC;AAEpE;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,WAAmB,EACnB,eAAuB,EACvB,KAAa;IAEb,MAAM,MAAM,GAAmB;QAC7B,UAAU,EAAE,KAAK,CAAC,MAAM;QACxB,SAAS,EAAE,CAAC;QACZ,OAAO,EAAE,CAAC;QACV,SAAS,EAAE,CAAC;QACZ,iBAAiB,EAAE,CAAC;QACpB,iBAAiB,EAAE,CAAC;QACpB,OAAO,EAAE,EAAE;KACZ,CAAC;IAEF,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACxC,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,SAAS;QACX,CAAC;QAED,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;YAC1B,MAAM,CAAC,iBAAiB,EAAE,CAAC;YAC3B,SAAS;QACX,CAAC;QAED,MAAM,UAAU,GAAG,MAAM,oBAAoB,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;QAEpE,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,CAAC,SAAS,EAAE,CAAC;YACnB,SAAS;QACX,CAAC;QAED,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;QAC9C,MAAM,YAAY,GAAG,kBAAkB,CAAC,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;QAEhF,qBAAqB;QACrB,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,MAAM,CAAC,SAAS,EAAE,CAAC;YACnB,SAAS;QACX,CAAC;QACD,oBAAoB;QAEpB,MAAM,CAAC,SAAS,EAAE,CAAC;QACnB,MAAM,CAAC,iBAAiB,IAAI,UAAU,CAAC,QAAQ,CAAC;QAEhD,MAAM,MAAM,GAAmB;YAC7B,MAAM,EAAE,IAAI,CAAC,EAAE;YACf,OAAO,EAAE,YAAY;SACtB,CAAC;QACF,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,WAAmB,EACnB,IAAU;IAEV,MAAM,UAAU,GAAG,MAAM,oBAAoB,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;IACpE,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,kBAAkB,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;AAChG,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { Actuals } from '../../types/estimation.js';
2
+ import type { ActualsWip, ConsolidatedActuals, DerivedActuals } from '../../types/index.js';
3
+ /**
4
+ * Consolidates multiple actuals sources with priority:
5
+ * explicit > WIP > git-derived
6
+ *
7
+ * Returns null when no actuals source has any data.
8
+ */
9
+ export declare function consolidateActuals(specEstimation: {
10
+ hourlyRate: number;
11
+ }, explicitActuals?: Actuals | null, wipData?: ActualsWip | null, gitDerived?: DerivedActuals | null): ConsolidatedActuals | null;
12
+ //# sourceMappingURL=consolidator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consolidator.d.ts","sourceRoot":"","sources":["../../../src/engine/actuals/consolidator.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE5F;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,cAAc,EAAE;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,EACtC,eAAe,CAAC,EAAE,OAAO,GAAG,IAAI,EAChC,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,EAC3B,UAAU,CAAC,EAAE,cAAc,GAAG,IAAI,GACjC,mBAAmB,GAAG,IAAI,CAkB5B"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Consolidates multiple actuals sources with priority:
3
+ * explicit > WIP > git-derived
4
+ *
5
+ * Returns null when no actuals source has any data.
6
+ */
7
+ export function consolidateActuals(specEstimation, explicitActuals, wipData, gitDerived) {
8
+ if (explicitActuals) {
9
+ return {
10
+ ...explicitActuals,
11
+ source: 'explicit',
12
+ confidence: 'high',
13
+ };
14
+ }
15
+ if (wipData) {
16
+ return convertWipToConsolidated(wipData);
17
+ }
18
+ if (gitDerived) {
19
+ return convertGitDerivedToConsolidated(specEstimation, gitDerived);
20
+ }
21
+ return null;
22
+ }
23
+ function convertWipToConsolidated(wip) {
24
+ return {
25
+ devHours: 0,
26
+ reviewHours: 0,
27
+ tokensOpus: wip.tokensOpus,
28
+ tokensSonnet: wip.tokensSonnet,
29
+ apiCostUsd: wip.apiCostAccumulated,
30
+ humanCostUsd: 0,
31
+ totalCostUsd: wip.apiCostAccumulated,
32
+ completedAt: wip.lastUpdated,
33
+ notes: wip.notes,
34
+ source: 'wip',
35
+ confidence: 'medium',
36
+ };
37
+ }
38
+ function convertGitDerivedToConsolidated(specEstimation, gitDerived) {
39
+ const humanCostUsd = gitDerived.devHours * specEstimation.hourlyRate;
40
+ return {
41
+ devHours: gitDerived.devHours,
42
+ reviewHours: gitDerived.reviewHours,
43
+ tokensOpus: 0,
44
+ tokensSonnet: 0,
45
+ apiCostUsd: 0,
46
+ humanCostUsd,
47
+ totalCostUsd: humanCostUsd,
48
+ completedAt: gitDerived.completedAt,
49
+ notes: `Derived from ${gitDerived.commitCount} git commit(s), ${gitDerived.linesChanged} lines changed`,
50
+ source: 'git-derived',
51
+ confidence: 'low',
52
+ };
53
+ }
54
+ //# sourceMappingURL=consolidator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consolidator.js","sourceRoot":"","sources":["../../../src/engine/actuals/consolidator.ts"],"names":[],"mappings":"AAKA;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAChC,cAAsC,EACtC,eAAgC,EAChC,OAA2B,EAC3B,UAAkC;IAElC,IAAI,eAAe,EAAE,CAAC;QACpB,OAAO;YACL,GAAG,eAAe;YAClB,MAAM,EAAE,UAAU;YAClB,UAAU,EAAE,MAAM;SACnB,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,wBAAwB,CAAC,OAAO,CAAC,CAAC;IAC3C,CAAC;IAED,IAAI,UAAU,EAAE,CAAC;QACf,OAAO,+BAA+B,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;IACrE,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,wBAAwB,CAAC,GAAe;IAC/C,OAAO;QACL,QAAQ,EAAE,CAAC;QACX,WAAW,EAAE,CAAC;QACd,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,YAAY,EAAE,GAAG,CAAC,YAAY;QAC9B,UAAU,EAAE,GAAG,CAAC,kBAAkB;QAClC,YAAY,EAAE,CAAC;QACf,YAAY,EAAE,GAAG,CAAC,kBAAkB;QACpC,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,MAAM,EAAE,KAAK;QACb,UAAU,EAAE,QAAQ;KACrB,CAAC;AACJ,CAAC;AAED,SAAS,+BAA+B,CACtC,cAAsC,EACtC,UAA0B;IAE1B,MAAM,YAAY,GAAG,UAAU,CAAC,QAAQ,GAAG,cAAc,CAAC,UAAU,CAAC;IACrE,OAAO;QACL,QAAQ,EAAE,UAAU,CAAC,QAAQ;QAC7B,WAAW,EAAE,UAAU,CAAC,WAAW;QACnC,UAAU,EAAE,CAAC;QACb,YAAY,EAAE,CAAC;QACf,UAAU,EAAE,CAAC;QACb,YAAY;QACZ,YAAY,EAAE,YAAY;QAC1B,WAAW,EAAE,UAAU,CAAC,WAAW;QACnC,KAAK,EAAE,gBAAgB,UAAU,CAAC,WAAW,mBAAmB,UAAU,CAAC,YAAY,gBAAgB;QACvG,MAAM,EAAE,aAAa;QACrB,UAAU,EAAE,KAAK;KAClB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { DerivedActuals, GitCommitInfo } from '../../types/index.js';
2
+ /**
3
+ * Parses git log output in the format produced by:
4
+ * git log --format="%H|%aI|%s" --numstat
5
+ */
6
+ export declare function parseGitLogOutput(output: string): GitCommitInfo[];
7
+ /**
8
+ * Calculates estimated dev hours from commits.
9
+ * Sums intervals between consecutive commits, capping each at 2h
10
+ * (gaps > 2h are treated as overnight pauses and ignored).
11
+ * Minimum 1h for single-commit specs.
12
+ */
13
+ export declare function calculateDevHours(commits: GitCommitInfo[]): number;
14
+ /**
15
+ * Sums insertions + deletions across all commits.
16
+ */
17
+ export declare function calculateLinesChanged(commits: GitCommitInfo[]): number;
18
+ /**
19
+ * Derives actuals for a spec by running git log filtered by specId in commit messages.
20
+ * Returns null if specId is invalid or no commits are found.
21
+ */
22
+ export declare function deriveActualsFromGit(projectPath: string, specId: string): Promise<DerivedActuals | null>;
23
+ //# sourceMappingURL=git-analyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-analyzer.d.ts","sourceRoot":"","sources":["../../../src/engine/actuals/git-analyzer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAO1E;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,EAAE,CAmBjE;AAqED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,MAAM,CA6BlE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,MAAM,CAEtE;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAwChC"}
@@ -0,0 +1,169 @@
1
+ // SpecForge — engine/actuals/git-analyzer.ts (SPEC-061)
2
+ // Derives actuals from git log history for a given spec.
3
+ import { execSync } from 'node:child_process';
4
+ const SPEC_ID_PATTERN = /^SPEC-\d+/;
5
+ const MAX_SESSION_GAP_MS = 2 * 60 * 60 * 1000; // 2 hours
6
+ const REVIEW_HOURS_RATIO = 0.15;
7
+ const MINIMUM_DEV_HOURS = 1;
8
+ /**
9
+ * Parses git log output in the format produced by:
10
+ * git log --format="%H|%aI|%s" --numstat
11
+ */
12
+ export function parseGitLogOutput(output) {
13
+ const commits = [];
14
+ const lines = output.split('\n');
15
+ let i = 0;
16
+ while (i < lines.length) {
17
+ const result = parseNextCommit(lines, i);
18
+ if (result === null) {
19
+ i++;
20
+ continue;
21
+ }
22
+ const { commit, nextIndex } = result;
23
+ if (commit) {
24
+ commits.push(commit);
25
+ }
26
+ i = nextIndex;
27
+ }
28
+ return commits;
29
+ }
30
+ function parseNextCommit(lines, startIndex) {
31
+ const headerLine = lines[startIndex];
32
+ if (!headerLine?.trim()) {
33
+ return null;
34
+ }
35
+ const headerParts = headerLine.split('|');
36
+ if (headerParts.length < 3) {
37
+ return null;
38
+ }
39
+ /* v8 ignore start */
40
+ const hash = headerParts[0] ?? '';
41
+ const timestamp = headerParts[1] ?? '';
42
+ /* v8 ignore stop */
43
+ const message = headerParts.slice(2).join('|');
44
+ const { insertions, deletions, nextIndex } = parseNumstat(lines, startIndex + 1);
45
+ const commit = hash.length === 40 ? { hash, timestamp, message, insertions, deletions } : null;
46
+ return { commit, nextIndex };
47
+ }
48
+ function parseNumstat(lines, startIndex) {
49
+ let i = startIndex;
50
+ let insertions = 0;
51
+ let deletions = 0;
52
+ // Skip optional blank line separator after header
53
+ if (lines[i]?.trim() === '') {
54
+ i++;
55
+ }
56
+ // Parse numstat lines (insertions<TAB>deletions<TAB>filename)
57
+ while (i < lines.length) {
58
+ const statLine = lines[i];
59
+ if (!statLine?.trim()) {
60
+ i++;
61
+ break;
62
+ }
63
+ // Next header line detection: 40-char hex hash followed by |
64
+ if (/^[0-9a-f]{40}\|/.test(statLine)) {
65
+ break;
66
+ }
67
+ const statParts = statLine.split('\t');
68
+ /* v8 ignore start */
69
+ const ins = parseInt(statParts[0] ?? '0', 10);
70
+ const del = parseInt(statParts[1] ?? '0', 10);
71
+ /* v8 ignore stop */
72
+ if (!isNaN(ins)) {
73
+ insertions += ins;
74
+ }
75
+ if (!isNaN(del)) {
76
+ deletions += del;
77
+ }
78
+ i++;
79
+ }
80
+ return { insertions, deletions, nextIndex: i };
81
+ }
82
+ /**
83
+ * Calculates estimated dev hours from commits.
84
+ * Sums intervals between consecutive commits, capping each at 2h
85
+ * (gaps > 2h are treated as overnight pauses and ignored).
86
+ * Minimum 1h for single-commit specs.
87
+ */
88
+ export function calculateDevHours(commits) {
89
+ if (commits.length === 0) {
90
+ return 0;
91
+ }
92
+ if (commits.length === 1) {
93
+ return MINIMUM_DEV_HOURS;
94
+ }
95
+ const sorted = [...commits].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
96
+ let totalMs = 0;
97
+ for (let i = 1; i < sorted.length; i++) {
98
+ const prev = sorted[i - 1];
99
+ const curr = sorted[i];
100
+ /* v8 ignore start */
101
+ if (!prev || !curr) {
102
+ continue;
103
+ }
104
+ /* v8 ignore stop */
105
+ const gap = new Date(curr.timestamp).getTime() - new Date(prev.timestamp).getTime();
106
+ if (gap > 0 && gap <= MAX_SESSION_GAP_MS) {
107
+ totalMs += gap;
108
+ }
109
+ }
110
+ const hours = totalMs / (1000 * 60 * 60);
111
+ return Math.max(MINIMUM_DEV_HOURS, Math.round(hours * 10) / 10);
112
+ }
113
+ /**
114
+ * Sums insertions + deletions across all commits.
115
+ */
116
+ export function calculateLinesChanged(commits) {
117
+ return commits.reduce((acc, c) => acc + c.insertions + c.deletions, 0);
118
+ }
119
+ /**
120
+ * Derives actuals for a spec by running git log filtered by specId in commit messages.
121
+ * Returns null if specId is invalid or no commits are found.
122
+ */
123
+ export function deriveActualsFromGit(projectPath, specId) {
124
+ if (!SPEC_ID_PATTERN.test(specId)) {
125
+ return Promise.resolve(null);
126
+ }
127
+ const gitOutput = runGitLog(projectPath, specId);
128
+ if (!gitOutput) {
129
+ return Promise.resolve(null);
130
+ }
131
+ const commits = parseGitLogOutput(gitOutput);
132
+ if (commits.length === 0) {
133
+ return Promise.resolve(null);
134
+ }
135
+ const sorted = [...commits].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
136
+ const firstCommit = sorted[0];
137
+ const lastCommit = sorted[sorted.length - 1];
138
+ /* v8 ignore start */
139
+ if (!firstCommit || !lastCommit) {
140
+ return Promise.resolve(null);
141
+ }
142
+ /* v8 ignore stop */
143
+ const devHours = calculateDevHours(commits);
144
+ const linesChanged = calculateLinesChanged(commits);
145
+ return Promise.resolve({
146
+ devHours,
147
+ reviewHours: Math.round(devHours * REVIEW_HOURS_RATIO * 10) / 10,
148
+ commitCount: commits.length,
149
+ linesChanged,
150
+ firstCommitDate: firstCommit.timestamp,
151
+ lastCommitDate: lastCommit.timestamp,
152
+ completedAt: lastCommit.timestamp,
153
+ source: 'git-log',
154
+ });
155
+ }
156
+ function runGitLog(projectPath, specId) {
157
+ try {
158
+ const result = execSync(`git log --format="%H|%aI|%s" --numstat --all --grep="${specId}"`, {
159
+ cwd: projectPath,
160
+ encoding: 'utf-8',
161
+ stdio: ['pipe', 'pipe', 'pipe'],
162
+ });
163
+ return result.trim() || null;
164
+ }
165
+ catch {
166
+ return null;
167
+ }
168
+ }
169
+ //# sourceMappingURL=git-analyzer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-analyzer.js","sourceRoot":"","sources":["../../../src/engine/actuals/git-analyzer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export { parseGitLogOutput, calculateDevHours, calculateLinesChanged, deriveActualsFromGit, } from './git-analyzer.js';
2
+ export { getWipPath, readWip, writeWip, deleteWip, updateWip } from './wip-tracker.js';
3
+ export { consolidateActuals } from './consolidator.js';
4
+ export { backfillActualsFromGit, deriveAndConsolidateSingle } from './backfill.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/engine/actuals/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAEvF,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEvD,OAAO,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,7 @@
1
+ // SpecForge — engine/actuals/index.ts (SPEC-061)
2
+ // Barrel exports for the actuals engine module.
3
+ export { parseGitLogOutput, calculateDevHours, calculateLinesChanged, deriveActualsFromGit, } from './git-analyzer.js';
4
+ export { getWipPath, readWip, writeWip, deleteWip, updateWip } from './wip-tracker.js';
5
+ export { consolidateActuals } from './consolidator.js';
6
+ export { backfillActualsFromGit, deriveAndConsolidateSingle } from './backfill.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/engine/actuals/index.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,gDAAgD;AAEhD,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAEvF,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEvD,OAAO,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,25 @@
1
+ import type { ActualsWip } from '../../types/index.js';
2
+ /**
3
+ * Returns the path to the WIP JSON file for a given spec.
4
+ */
5
+ export declare function getWipPath(projectDataDir: string, specId: string): string;
6
+ /**
7
+ * Reads WIP actuals for a spec from disk.
8
+ * Returns null if the file does not exist or cannot be parsed.
9
+ */
10
+ export declare function readWip(projectDataDir: string, specId: string): ActualsWip | null;
11
+ /**
12
+ * Writes WIP actuals for a spec to disk.
13
+ * Creates the directory if it does not exist.
14
+ */
15
+ export declare function writeWip(projectDataDir: string, specId: string, wip: ActualsWip): void;
16
+ /**
17
+ * Deletes the WIP actuals file for a spec if it exists.
18
+ */
19
+ export declare function deleteWip(projectDataDir: string, specId: string): void;
20
+ /**
21
+ * Reads existing WIP, merges updates, and writes back.
22
+ * If no existing WIP, creates a new one with defaults merged with updates.
23
+ */
24
+ export declare function updateWip(projectDataDir: string, specId: string, updates: Partial<ActualsWip>): ActualsWip;
25
+ //# sourceMappingURL=wip-tracker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wip-tracker.d.ts","sourceRoot":"","sources":["../../../src/engine/actuals/wip-tracker.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD;;GAEG;AACH,wBAAgB,UAAU,CAAC,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEzE;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAQjF;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,IAAI,CAKtF;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAOtE;AAED;;;GAGG;AACH,wBAAgB,SAAS,CACvB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,CAAC,UAAU,CAAC,GAC3B,UAAU,CAsBZ"}
@@ -0,0 +1,71 @@
1
+ // SpecForge — engine/actuals/wip-tracker.ts (SPEC-061)
2
+ // Simple JSON file persistence for WIP actuals during implementation.
3
+ import * as fs from 'node:fs';
4
+ import * as path from 'node:path';
5
+ /**
6
+ * Returns the path to the WIP JSON file for a given spec.
7
+ */
8
+ export function getWipPath(projectDataDir, specId) {
9
+ return path.join(projectDataDir, 'actuals-wip', `${specId}.json`);
10
+ }
11
+ /**
12
+ * Reads WIP actuals for a spec from disk.
13
+ * Returns null if the file does not exist or cannot be parsed.
14
+ */
15
+ export function readWip(projectDataDir, specId) {
16
+ const filePath = getWipPath(projectDataDir, specId);
17
+ try {
18
+ const content = fs.readFileSync(filePath, { encoding: 'utf-8' });
19
+ return JSON.parse(content);
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ }
25
+ /**
26
+ * Writes WIP actuals for a spec to disk.
27
+ * Creates the directory if it does not exist.
28
+ */
29
+ export function writeWip(projectDataDir, specId, wip) {
30
+ const filePath = getWipPath(projectDataDir, specId);
31
+ const dir = path.dirname(filePath);
32
+ fs.mkdirSync(dir, { recursive: true });
33
+ fs.writeFileSync(filePath, JSON.stringify(wip, null, 2), { encoding: 'utf-8' });
34
+ }
35
+ /**
36
+ * Deletes the WIP actuals file for a spec if it exists.
37
+ */
38
+ export function deleteWip(projectDataDir, specId) {
39
+ const filePath = getWipPath(projectDataDir, specId);
40
+ try {
41
+ fs.unlinkSync(filePath);
42
+ }
43
+ catch {
44
+ // File doesn't exist — nothing to do
45
+ }
46
+ }
47
+ /**
48
+ * Reads existing WIP, merges updates, and writes back.
49
+ * If no existing WIP, creates a new one with defaults merged with updates.
50
+ */
51
+ export function updateWip(projectDataDir, specId, updates) {
52
+ const existing = readWip(projectDataDir, specId);
53
+ const now = new Date().toISOString();
54
+ const base = existing ?? {
55
+ specId,
56
+ startedAt: now,
57
+ tokensOpus: 0,
58
+ tokensSonnet: 0,
59
+ apiCostAccumulated: 0,
60
+ lastUpdated: now,
61
+ notes: '',
62
+ };
63
+ const merged = {
64
+ ...base,
65
+ ...updates,
66
+ lastUpdated: now,
67
+ };
68
+ writeWip(projectDataDir, specId, merged);
69
+ return merged;
70
+ }
71
+ //# sourceMappingURL=wip-tracker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wip-tracker.js","sourceRoot":"","sources":["../../../src/engine/actuals/wip-tracker.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,sEAAsE;AACtE,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAGlC;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,cAAsB,EAAE,MAAc;IAC/D,OAAO,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,aAAa,EAAE,GAAG,MAAM,OAAO,CAAC,CAAC;AACpE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,cAAsB,EAAE,MAAc;IAC5D,MAAM,QAAQ,GAAG,UAAU,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;IACpD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;QACjE,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAe,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,cAAsB,EAAE,MAAc,EAAE,GAAe;IAC9E,MAAM,QAAQ,GAAG,UAAU,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;IACpD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvC,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;AAClF,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,cAAsB,EAAE,MAAc;IAC9D,MAAM,QAAQ,GAAG,UAAU,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;IACpD,IAAI,CAAC;QACH,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,qCAAqC;IACvC,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CACvB,cAAsB,EACtB,MAAc,EACd,OAA4B;IAE5B,MAAM,QAAQ,GAAG,OAAO,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;IACjD,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAErC,MAAM,IAAI,GAAe,QAAQ,IAAI;QACnC,MAAM;QACN,SAAS,EAAE,GAAG;QACd,UAAU,EAAE,CAAC;QACb,YAAY,EAAE,CAAC;QACf,kBAAkB,EAAE,CAAC;QACrB,WAAW,EAAE,GAAG;QAChB,KAAK,EAAE,EAAE;KACV,CAAC;IAEF,MAAM,MAAM,GAAe;QACzB,GAAG,IAAI;QACP,GAAG,OAAO;QACV,WAAW,EAAE,GAAG;KACjB,CAAC;IAEF,QAAQ,CAAC,cAAc,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { Spec, ProjectKnowledge, ExecutiveSummarySpecEntry, AggregatedEstimation, RiskProfile, ArchitectureSnapshot, ExecutiveSummaryData } from '../../../types/index.js';
2
+ /** Map a Spec to an ExecutiveSummarySpecEntry. */
3
+ export declare function extractSpecEntries(specs: Spec[]): ExecutiveSummarySpecEntry[];
4
+ /** Sum all estimation fields across all specs. */
5
+ export declare function aggregateEstimation(specs: Spec[]): AggregatedEstimation;
6
+ /** Count specs by risk level; highlights = titles of high/critical specs. */
7
+ export declare function buildRiskProfile(specs: Spec[]): RiskProfile;
8
+ /** Extract architecture snapshot from ProjectKnowledge. */
9
+ export declare function buildArchitectureSnapshot(knowledge: ProjectKnowledge): ArchitectureSnapshot;
10
+ /** Orchestrate all extractors to build the full ExecutiveSummaryData. */
11
+ export declare function buildExecutiveSummaryData(knowledge: ProjectKnowledge, specs: Spec[]): ExecutiveSummaryData;
12
+ //# sourceMappingURL=data-extractors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-extractors.d.ts","sourceRoot":"","sources":["../../../../src/engine/doc-generator/executive-summary/data-extractors.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,IAAI,EACJ,gBAAgB,EAChB,yBAAyB,EACzB,oBAAoB,EACpB,WAAW,EACX,oBAAoB,EACpB,oBAAoB,EACrB,MAAM,yBAAyB,CAAC;AAEjC,kDAAkD;AAClD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,yBAAyB,EAAE,CAsB7E;AAED,kDAAkD;AAClD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,oBAAoB,CAuBvE;AAED,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,WAAW,CA2B3D;AAED,2DAA2D;AAC3D,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,gBAAgB,GAAG,oBAAoB,CAS3F;AAED,yEAAyE;AACzE,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,gBAAgB,EAC3B,KAAK,EAAE,IAAI,EAAE,GACZ,oBAAoB,CAYtB"}
@@ -0,0 +1,96 @@
1
+ /** Map a Spec to an ExecutiveSummarySpecEntry. */
2
+ export function extractSpecEntries(specs) {
3
+ return specs.map((spec) => {
4
+ const entry = {
5
+ id: spec.id,
6
+ title: spec.title,
7
+ status: spec.status,
8
+ difficulty: spec.difficulty,
9
+ risk: spec.risk,
10
+ devHours: spec.estimation.devHours,
11
+ reviewHours: spec.estimation.reviewHours,
12
+ totalCostUsd: spec.estimation.totalCostUsd,
13
+ tags: spec.tags,
14
+ };
15
+ if (spec.actuals !== null) {
16
+ entry.actualDevHours = spec.actuals.devHours;
17
+ entry.actualTotalCostUsd = spec.actuals.totalCostUsd;
18
+ entry.completedAt = spec.actuals.completedAt;
19
+ }
20
+ return entry;
21
+ });
22
+ }
23
+ /** Sum all estimation fields across all specs. */
24
+ export function aggregateEstimation(specs) {
25
+ let totalDevHours = 0;
26
+ let totalReviewHours = 0;
27
+ let totalHumanCostUsd = 0;
28
+ let totalApiCostUsd = 0;
29
+ let totalCostUsd = 0;
30
+ for (const spec of specs) {
31
+ totalDevHours += spec.estimation.devHours;
32
+ totalReviewHours += spec.estimation.reviewHours;
33
+ totalHumanCostUsd += spec.estimation.humanCostUsd;
34
+ totalApiCostUsd += spec.estimation.apiCostUsd;
35
+ totalCostUsd += spec.estimation.totalCostUsd;
36
+ }
37
+ return {
38
+ totalDevHours,
39
+ totalReviewHours,
40
+ totalHumanCostUsd,
41
+ totalApiCostUsd,
42
+ totalCostUsd,
43
+ specCount: specs.length,
44
+ };
45
+ }
46
+ /** Count specs by risk level; highlights = titles of high/critical specs. */
47
+ export function buildRiskProfile(specs) {
48
+ let low = 0;
49
+ let medium = 0;
50
+ let high = 0;
51
+ let critical = 0;
52
+ const highlights = [];
53
+ for (const spec of specs) {
54
+ switch (spec.risk) {
55
+ case 'low':
56
+ low++;
57
+ break;
58
+ case 'medium':
59
+ medium++;
60
+ break;
61
+ case 'high':
62
+ high++;
63
+ highlights.push(spec.title);
64
+ break;
65
+ case 'critical':
66
+ critical++;
67
+ highlights.push(spec.title);
68
+ break;
69
+ }
70
+ }
71
+ return { low, medium, high, critical, highlights };
72
+ }
73
+ /** Extract architecture snapshot from ProjectKnowledge. */
74
+ export function buildArchitectureSnapshot(knowledge) {
75
+ const layers = knowledge.architecture.layers.map((l) => l.name);
76
+ return {
77
+ language: knowledge.language,
78
+ framework: knowledge.framework ?? 'none',
79
+ database: knowledge.database,
80
+ layers,
81
+ };
82
+ }
83
+ /** Orchestrate all extractors to build the full ExecutiveSummaryData. */
84
+ export function buildExecutiveSummaryData(knowledge, specs) {
85
+ const pathSegments = knowledge.projectPath.split('/');
86
+ const projectName = pathSegments[pathSegments.length - 1] ?? knowledge.projectPath;
87
+ return {
88
+ projectName,
89
+ generatedAt: new Date().toISOString(),
90
+ architecture: buildArchitectureSnapshot(knowledge),
91
+ specs: extractSpecEntries(specs),
92
+ estimation: aggregateEstimation(specs),
93
+ riskProfile: buildRiskProfile(specs),
94
+ };
95
+ }
96
+ //# sourceMappingURL=data-extractors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-extractors.js","sourceRoot":"","sources":["../../../../src/engine/doc-generator/executive-summary/data-extractors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { ProjectKnowledge, Spec, GeneratedDocument } from '../../../types/index.js';
2
+ /**
3
+ * Generate a self-contained HTML executive summary document.
4
+ * Returns a GeneratedDocument with type 'executive-summary'.
5
+ */
6
+ export declare function generateExecutiveSummary(knowledge: ProjectKnowledge, specs: Spec[], _language: string): GeneratedDocument;
7
+ //# sourceMappingURL=executive-summary-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executive-summary-generator.d.ts","sourceRoot":"","sources":["../../../../src/engine/doc-generator/executive-summary/executive-summary-generator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAmBzF;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,gBAAgB,EAC3B,KAAK,EAAE,IAAI,EAAE,EACb,SAAS,EAAE,MAAM,GAChB,iBAAiB,CAiCnB"}