@eddacraft/anvil-core 0.1.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 (215) hide show
  1. package/LICENSE +14 -0
  2. package/dist/antipattern/index.d.ts +11 -0
  3. package/dist/antipattern/index.d.ts.map +1 -0
  4. package/dist/antipattern/index.js +31 -0
  5. package/dist/antipattern/patterns-css.d.ts +17 -0
  6. package/dist/antipattern/patterns-css.d.ts.map +1 -0
  7. package/dist/antipattern/patterns-css.js +72 -0
  8. package/dist/antipattern/patterns-html.d.ts +21 -0
  9. package/dist/antipattern/patterns-html.d.ts.map +1 -0
  10. package/dist/antipattern/patterns-html.js +139 -0
  11. package/dist/antipattern/patterns.d.ts +72 -0
  12. package/dist/antipattern/patterns.d.ts.map +1 -0
  13. package/dist/antipattern/patterns.js +301 -0
  14. package/dist/antipattern/scanner.d.ts +32 -0
  15. package/dist/antipattern/scanner.d.ts.map +1 -0
  16. package/dist/antipattern/scanner.js +89 -0
  17. package/dist/antipattern/types.d.ts +318 -0
  18. package/dist/antipattern/types.d.ts.map +1 -0
  19. package/dist/antipattern/types.js +278 -0
  20. package/dist/architecture/analyzer.d.ts +123 -0
  21. package/dist/architecture/analyzer.d.ts.map +1 -0
  22. package/dist/architecture/analyzer.js +321 -0
  23. package/dist/architecture/baseline.d.ts +112 -0
  24. package/dist/architecture/baseline.d.ts.map +1 -0
  25. package/dist/architecture/baseline.js +245 -0
  26. package/dist/architecture/compiler.d.ts +24 -0
  27. package/dist/architecture/compiler.d.ts.map +1 -0
  28. package/dist/architecture/compiler.js +57 -0
  29. package/dist/architecture/context.d.ts +129 -0
  30. package/dist/architecture/context.d.ts.map +1 -0
  31. package/dist/architecture/context.js +116 -0
  32. package/dist/architecture/dc-generator.d.ts +9 -0
  33. package/dist/architecture/dc-generator.d.ts.map +1 -0
  34. package/dist/architecture/dc-generator.js +220 -0
  35. package/dist/architecture/definition-schema.d.ts +128 -0
  36. package/dist/architecture/definition-schema.d.ts.map +1 -0
  37. package/dist/architecture/definition-schema.js +94 -0
  38. package/dist/architecture/edge-detector-html.d.ts +6 -0
  39. package/dist/architecture/edge-detector-html.d.ts.map +1 -0
  40. package/dist/architecture/edge-detector-html.js +5 -0
  41. package/dist/architecture/edge-detector-web.d.ts +32 -0
  42. package/dist/architecture/edge-detector-web.d.ts.map +1 -0
  43. package/dist/architecture/edge-detector-web.js +133 -0
  44. package/dist/architecture/edge-detector.d.ts +116 -0
  45. package/dist/architecture/edge-detector.d.ts.map +1 -0
  46. package/dist/architecture/edge-detector.js +229 -0
  47. package/dist/architecture/entry-detector.d.ts +44 -0
  48. package/dist/architecture/entry-detector.d.ts.map +1 -0
  49. package/dist/architecture/entry-detector.js +263 -0
  50. package/dist/architecture/index.d.ts +21 -0
  51. package/dist/architecture/index.d.ts.map +1 -0
  52. package/dist/architecture/index.js +48 -0
  53. package/dist/architecture/layer-detector.d.ts +60 -0
  54. package/dist/architecture/layer-detector.d.ts.map +1 -0
  55. package/dist/architecture/layer-detector.js +331 -0
  56. package/dist/architecture/rego-generator.d.ts +25 -0
  57. package/dist/architecture/rego-generator.d.ts.map +1 -0
  58. package/dist/architecture/rego-generator.js +229 -0
  59. package/dist/architecture/templates/index.d.ts +39 -0
  60. package/dist/architecture/templates/index.d.ts.map +1 -0
  61. package/dist/architecture/templates/index.js +124 -0
  62. package/dist/architecture/types.d.ts +280 -0
  63. package/dist/architecture/types.d.ts.map +1 -0
  64. package/dist/architecture/types.js +269 -0
  65. package/dist/architecture/yaml-parser.d.ts +13 -0
  66. package/dist/architecture/yaml-parser.d.ts.map +1 -0
  67. package/dist/architecture/yaml-parser.js +234 -0
  68. package/dist/config/constants.d.ts +9 -0
  69. package/dist/config/constants.d.ts.map +1 -0
  70. package/dist/config/constants.js +20 -0
  71. package/dist/config/index.d.ts +9 -0
  72. package/dist/config/index.d.ts.map +1 -0
  73. package/dist/config/index.js +8 -0
  74. package/dist/config/loader.d.ts +41 -0
  75. package/dist/config/loader.d.ts.map +1 -0
  76. package/dist/config/loader.js +76 -0
  77. package/dist/config/nudge-config.d.ts +35 -0
  78. package/dist/config/nudge-config.d.ts.map +1 -0
  79. package/dist/config/nudge-config.js +34 -0
  80. package/dist/config/types.d.ts +30 -0
  81. package/dist/config/types.d.ts.map +1 -0
  82. package/dist/config/types.js +4 -0
  83. package/dist/contracts/index.d.ts +14 -0
  84. package/dist/contracts/index.d.ts.map +1 -0
  85. package/dist/contracts/index.js +13 -0
  86. package/dist/contracts/schemas/aps.schema.d.ts +269 -0
  87. package/dist/contracts/schemas/aps.schema.d.ts.map +1 -0
  88. package/dist/contracts/schemas/aps.schema.js +183 -0
  89. package/dist/contracts/schemas/index.d.ts +12 -0
  90. package/dist/contracts/schemas/index.d.ts.map +1 -0
  91. package/dist/contracts/schemas/index.js +14 -0
  92. package/dist/contracts/schemas/json-schema.d.ts +14 -0
  93. package/dist/contracts/schemas/json-schema.d.ts.map +1 -0
  94. package/dist/contracts/schemas/json-schema.js +31 -0
  95. package/dist/contracts/schemas/warning.schema.d.ts +171 -0
  96. package/dist/contracts/schemas/warning.schema.d.ts.map +1 -0
  97. package/dist/contracts/schemas/warning.schema.js +123 -0
  98. package/dist/contracts/types/gate.types.d.ts +194 -0
  99. package/dist/contracts/types/gate.types.d.ts.map +1 -0
  100. package/dist/contracts/types/gate.types.js +19 -0
  101. package/dist/contracts/types/index.d.ts +9 -0
  102. package/dist/contracts/types/index.d.ts.map +1 -0
  103. package/dist/contracts/types/index.js +8 -0
  104. package/dist/crypto/hash.d.ts +47 -0
  105. package/dist/crypto/hash.d.ts.map +1 -0
  106. package/dist/crypto/hash.js +110 -0
  107. package/dist/crypto/index.d.ts +7 -0
  108. package/dist/crypto/index.d.ts.map +1 -0
  109. package/dist/crypto/index.js +6 -0
  110. package/dist/drift/index.d.ts +6 -0
  111. package/dist/drift/index.d.ts.map +1 -0
  112. package/dist/drift/index.js +5 -0
  113. package/dist/drift/report-generator.d.ts +21 -0
  114. package/dist/drift/report-generator.d.ts.map +1 -0
  115. package/dist/drift/report-generator.js +240 -0
  116. package/dist/drift/snapshot-capture.d.ts +26 -0
  117. package/dist/drift/snapshot-capture.d.ts.map +1 -0
  118. package/dist/drift/snapshot-capture.js +195 -0
  119. package/dist/drift/snapshot-compare.d.ts +50 -0
  120. package/dist/drift/snapshot-compare.d.ts.map +1 -0
  121. package/dist/drift/snapshot-compare.js +142 -0
  122. package/dist/drift/snapshot-schema.d.ts +197 -0
  123. package/dist/drift/snapshot-schema.d.ts.map +1 -0
  124. package/dist/drift/snapshot-schema.js +193 -0
  125. package/dist/drift/snapshot-storage.d.ts +25 -0
  126. package/dist/drift/snapshot-storage.d.ts.map +1 -0
  127. package/dist/drift/snapshot-storage.js +179 -0
  128. package/dist/explain/antipattern-explainer.d.ts +4 -0
  129. package/dist/explain/antipattern-explainer.d.ts.map +1 -0
  130. package/dist/explain/antipattern-explainer.js +196 -0
  131. package/dist/explain/boundary-explainer.d.ts +5 -0
  132. package/dist/explain/boundary-explainer.d.ts.map +1 -0
  133. package/dist/explain/boundary-explainer.js +261 -0
  134. package/dist/explain/explain-service.d.ts +19 -0
  135. package/dist/explain/explain-service.d.ts.map +1 -0
  136. package/dist/explain/explain-service.js +106 -0
  137. package/dist/explain/index.d.ts +7 -0
  138. package/dist/explain/index.d.ts.map +1 -0
  139. package/dist/explain/index.js +5 -0
  140. package/dist/explain/template-loader.d.ts +9 -0
  141. package/dist/explain/template-loader.d.ts.map +1 -0
  142. package/dist/explain/template-loader.js +51 -0
  143. package/dist/explain/types.d.ts +46 -0
  144. package/dist/explain/types.d.ts.map +1 -0
  145. package/dist/explain/types.js +31 -0
  146. package/dist/index.d.ts +26 -0
  147. package/dist/index.d.ts.map +1 -0
  148. package/dist/index.js +37 -0
  149. package/dist/provenance/collector.d.ts +86 -0
  150. package/dist/provenance/collector.d.ts.map +1 -0
  151. package/dist/provenance/collector.js +425 -0
  152. package/dist/provenance/git-ai-standard/git-notes.d.ts +85 -0
  153. package/dist/provenance/git-ai-standard/git-notes.d.ts.map +1 -0
  154. package/dist/provenance/git-ai-standard/git-notes.js +292 -0
  155. package/dist/provenance/git-ai-standard/index.d.ts +44 -0
  156. package/dist/provenance/git-ai-standard/index.d.ts.map +1 -0
  157. package/dist/provenance/git-ai-standard/index.js +47 -0
  158. package/dist/provenance/git-ai-standard/serializer.d.ts +54 -0
  159. package/dist/provenance/git-ai-standard/serializer.d.ts.map +1 -0
  160. package/dist/provenance/git-ai-standard/serializer.js +224 -0
  161. package/dist/provenance/git-ai-standard/session.d.ts +51 -0
  162. package/dist/provenance/git-ai-standard/session.d.ts.map +1 -0
  163. package/dist/provenance/git-ai-standard/session.js +118 -0
  164. package/dist/provenance/git-ai-standard/types.d.ts +173 -0
  165. package/dist/provenance/git-ai-standard/types.d.ts.map +1 -0
  166. package/dist/provenance/git-ai-standard/types.js +109 -0
  167. package/dist/provenance/index.d.ts +5 -0
  168. package/dist/provenance/index.d.ts.map +1 -0
  169. package/dist/provenance/index.js +6 -0
  170. package/dist/provenance/store.d.ts +83 -0
  171. package/dist/provenance/store.d.ts.map +1 -0
  172. package/dist/provenance/store.js +248 -0
  173. package/dist/provenance/types.d.ts +160 -0
  174. package/dist/provenance/types.d.ts.map +1 -0
  175. package/dist/provenance/types.js +112 -0
  176. package/dist/suppression/index.d.ts +4 -0
  177. package/dist/suppression/index.d.ts.map +1 -0
  178. package/dist/suppression/index.js +3 -0
  179. package/dist/suppression/parser.d.ts +31 -0
  180. package/dist/suppression/parser.d.ts.map +1 -0
  181. package/dist/suppression/parser.js +219 -0
  182. package/dist/suppression/service.d.ts +29 -0
  183. package/dist/suppression/service.d.ts.map +1 -0
  184. package/dist/suppression/service.js +132 -0
  185. package/dist/suppression/store.d.ts +61 -0
  186. package/dist/suppression/store.d.ts.map +1 -0
  187. package/dist/suppression/store.js +169 -0
  188. package/dist/utils/debug.d.ts +48 -0
  189. package/dist/utils/debug.d.ts.map +1 -0
  190. package/dist/utils/debug.js +100 -0
  191. package/dist/utils/index.d.ts +4 -0
  192. package/dist/utils/index.d.ts.map +1 -0
  193. package/dist/utils/index.js +3 -0
  194. package/dist/utils/path-safety.d.ts +21 -0
  195. package/dist/utils/path-safety.d.ts.map +1 -0
  196. package/dist/utils/path-safety.js +49 -0
  197. package/dist/utils/severity.d.ts +37 -0
  198. package/dist/utils/severity.d.ts.map +1 -0
  199. package/dist/utils/severity.js +22 -0
  200. package/dist/validation/aps-validator.d.ts +66 -0
  201. package/dist/validation/aps-validator.d.ts.map +1 -0
  202. package/dist/validation/aps-validator.js +173 -0
  203. package/dist/validation/errors.d.ts +52 -0
  204. package/dist/validation/errors.d.ts.map +1 -0
  205. package/dist/validation/errors.js +115 -0
  206. package/dist/validation/index.d.ts +8 -0
  207. package/dist/validation/index.d.ts.map +1 -0
  208. package/dist/validation/index.js +13 -0
  209. package/dist/warnings/index.d.ts +2 -0
  210. package/dist/warnings/index.d.ts.map +1 -0
  211. package/dist/warnings/index.js +1 -0
  212. package/dist/warnings/warning-id.d.ts +180 -0
  213. package/dist/warnings/warning-id.d.ts.map +1 -0
  214. package/dist/warnings/warning-id.js +257 -0
  215. package/package.json +79 -0
@@ -0,0 +1,86 @@
1
+ import type { AITool, Environment, GitContext, CheckSummary, ProvenanceRecord } from './types.js';
2
+ import type { GateRunResult } from '../contracts/index.js';
3
+ import { type AuthorshipLog } from './git-ai-standard/index.js';
4
+ /**
5
+ * Collects environment information
6
+ */
7
+ export declare function collectEnvironment(workspaceRoot: string): Promise<Environment>;
8
+ /**
9
+ * Collects git context information
10
+ */
11
+ export declare function collectGitContext(workspaceRoot: string): Promise<GitContext | undefined>;
12
+ /**
13
+ * Detects which AI coding tool might have been used
14
+ */
15
+ export declare function detectAITool(workspaceRoot: string): Promise<AITool | undefined>;
16
+ /**
17
+ * Converts gate results to check summaries
18
+ */
19
+ export declare function summariseChecks(results: GateRunResult): CheckSummary[];
20
+ /**
21
+ * Creates a full provenance record from a gate run
22
+ */
23
+ export declare function createProvenanceRecord(params: {
24
+ workspaceRoot: string;
25
+ filesChecked: string[];
26
+ scope: ProvenanceRecord['scope'];
27
+ results: GateRunResult;
28
+ trigger: ProvenanceRecord['trigger'];
29
+ startTime: number;
30
+ planId?: string;
31
+ parentId?: string;
32
+ }): Promise<ProvenanceRecord>;
33
+ /**
34
+ * Formats a provenance record for display
35
+ */
36
+ export declare function formatProvenanceRecord(record: ProvenanceRecord): string;
37
+ /**
38
+ * Create an AuthorshipLog from provenance context
39
+ *
40
+ * This bridges the Anvil provenance system with the Git AI Standard v3.0.0,
41
+ * enabling AI-generated code to be tracked in Git Notes.
42
+ *
43
+ * @param params - Parameters for creating the authorship log
44
+ * @param params.commitSha - Full 40-character commit SHA (use `git rev-parse` to resolve short refs)
45
+ * @returns AuthorshipLog if AI tool is detected, null otherwise
46
+ * @throws Error if commitSha is not a valid 40-character hex SHA
47
+ */
48
+ export declare function createAuthorshipLog(params: {
49
+ commitSha: string;
50
+ fileLineMap: Record<string, string>;
51
+ messages: Array<{
52
+ type: 'user' | 'assistant';
53
+ text: string;
54
+ }>;
55
+ humanAuthor?: string;
56
+ totalAdditions?: number;
57
+ totalDeletions?: number;
58
+ }): AuthorshipLog | null;
59
+ /**
60
+ * Attach an AuthorshipLog to a commit via Git Notes
61
+ *
62
+ * @param log - The authorship log to attach
63
+ * @param workspaceRoot - The repository root directory
64
+ */
65
+ export declare function attachAuthorshipToCommit(log: AuthorshipLog, workspaceRoot: string): Promise<void>;
66
+ /**
67
+ * Create and attach an AuthorshipLog in one operation
68
+ *
69
+ * Convenience function that combines createAuthorshipLog and attachAuthorshipToCommit.
70
+ *
71
+ * @param params - Parameters for creating the authorship log
72
+ * @param workspaceRoot - The repository root directory
73
+ * @returns true if log was created and attached, false if no AI agent detected
74
+ */
75
+ export declare function recordAIAuthorship(params: {
76
+ commitSha: string;
77
+ fileLineMap: Record<string, string>;
78
+ messages: Array<{
79
+ type: 'user' | 'assistant';
80
+ text: string;
81
+ }>;
82
+ humanAuthor?: string;
83
+ totalAdditions?: number;
84
+ totalDeletions?: number;
85
+ }, workspaceRoot: string): Promise<boolean>;
86
+ //# sourceMappingURL=collector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collector.d.ts","sourceRoot":"","sources":["../../src/provenance/collector.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAClG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,EAKL,KAAK,aAAa,EAGnB,MAAM,4BAA4B,CAAC;AAUpC;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAsBpF;AAiCD;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAmE9F;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAqGrF;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,aAAa,GAAG,YAAY,EAAE,CAStE;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAAC,MAAM,EAAE;IACnD,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACjC,OAAO,EAAE,aAAa,CAAC;IACvB,OAAO,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA8C5B;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAqDvE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,GAAG,aAAa,GAAG,IAAI,CA0DvB;AAED;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE,aAAa,EAClB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC,CAIf;AAED;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE;IACN,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,EACD,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,OAAO,CAAC,CAMlB"}
@@ -0,0 +1,425 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ import { existsSync, readFileSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { randomUUID } from 'node:crypto';
6
+ import { createDebugger } from '../utils/debug.js';
7
+ import { generateSessionHash, detectCurrentAgent, writeAuthorshipNote, SCHEMA_VERSION, } from './git-ai-standard/index.js';
8
+ const debug = createDebugger('provenance');
9
+ const execFileAsync = promisify(execFile);
10
+ function generateProvenanceId() {
11
+ return `prov-${randomUUID()}`;
12
+ }
13
+ /**
14
+ * Collects environment information
15
+ */
16
+ export async function collectEnvironment(workspaceRoot) {
17
+ let anvilVersion = '0.0.0';
18
+ // Try to get Anvil version from package.json
19
+ try {
20
+ const pkgPath = join(workspaceRoot, 'node_modules', '@anvil', 'cli', 'package.json');
21
+ if (existsSync(pkgPath)) {
22
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
23
+ anvilVersion = pkg.version || '0.0.0';
24
+ }
25
+ }
26
+ catch (error) {
27
+ debug('Failed to read Anvil CLI package.json for version', error);
28
+ }
29
+ return {
30
+ os: `${process.platform}-${process.arch}`,
31
+ node_version: process.version,
32
+ anvil_version: anvilVersion,
33
+ cwd: workspaceRoot,
34
+ ci: detectCIEnvironment(),
35
+ ci_provider: detectCIProvider(),
36
+ };
37
+ }
38
+ /**
39
+ * Detects if running in a CI environment
40
+ */
41
+ function detectCIEnvironment() {
42
+ return !!(process.env.CI ||
43
+ process.env.CONTINUOUS_INTEGRATION ||
44
+ process.env.BUILD_NUMBER ||
45
+ process.env.GITHUB_ACTIONS ||
46
+ process.env.GITLAB_CI ||
47
+ process.env.CIRCLECI ||
48
+ process.env.JENKINS_URL ||
49
+ process.env.TRAVIS);
50
+ }
51
+ /**
52
+ * Detects the CI provider
53
+ */
54
+ function detectCIProvider() {
55
+ if (process.env.GITHUB_ACTIONS)
56
+ return 'github-actions';
57
+ if (process.env.GITLAB_CI)
58
+ return 'gitlab-ci';
59
+ if (process.env.CIRCLECI)
60
+ return 'circleci';
61
+ if (process.env.JENKINS_URL)
62
+ return 'jenkins';
63
+ if (process.env.TRAVIS)
64
+ return 'travis-ci';
65
+ if (process.env.BUILDKITE)
66
+ return 'buildkite';
67
+ if (process.env.AZURE_PIPELINES)
68
+ return 'azure-pipelines';
69
+ if (process.env.BITBUCKET_PIPELINE)
70
+ return 'bitbucket-pipelines';
71
+ return undefined;
72
+ }
73
+ /**
74
+ * Collects git context information
75
+ */
76
+ export async function collectGitContext(workspaceRoot) {
77
+ // Check if this is a git repository
78
+ if (!existsSync(join(workspaceRoot, '.git'))) {
79
+ return undefined;
80
+ }
81
+ try {
82
+ const [branch, commit, commitMessage, author, status, stagedFiles] = await Promise.all([
83
+ execFileAsync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: workspaceRoot })
84
+ .then((r) => r.stdout.trim())
85
+ .catch(() => undefined),
86
+ execFileAsync('git', ['rev-parse', 'HEAD'], { cwd: workspaceRoot })
87
+ .then((r) => r.stdout.trim())
88
+ .catch(() => undefined),
89
+ execFileAsync('git', ['log', '-1', '--format=%s'], { cwd: workspaceRoot })
90
+ .then((r) => r.stdout.trim())
91
+ .catch(() => undefined),
92
+ execFileAsync('git', ['log', '-1', '--format=%an <%ae>'], { cwd: workspaceRoot })
93
+ .then((r) => r.stdout.trim())
94
+ .catch(() => undefined),
95
+ execFileAsync('git', ['status', '--porcelain'], { cwd: workspaceRoot })
96
+ .then((r) => r.stdout.trim())
97
+ .catch(() => ''),
98
+ execFileAsync('git', ['diff', '--name-only', '--cached'], { cwd: workspaceRoot })
99
+ .then((r) => r.stdout
100
+ .trim()
101
+ .split('\n')
102
+ .filter((f) => f))
103
+ .catch(() => []),
104
+ ]);
105
+ // Parse modified files from git status --porcelain
106
+ // Format is "XY filename" where X and Y are status chars (may be spaces)
107
+ // Use regex to extract filename after the 2-char status + space
108
+ const modifiedFiles = status
109
+ .split('\n')
110
+ .filter((line) => line.trim())
111
+ .map((line) => line.replace(/^.{0,2}\s/, '').trim())
112
+ .filter((f) => f);
113
+ // Try to get repository URL
114
+ let repository;
115
+ try {
116
+ const { stdout } = await execFileAsync('git', ['remote', 'get-url', 'origin'], {
117
+ cwd: workspaceRoot,
118
+ });
119
+ repository = stdout.trim();
120
+ }
121
+ catch (error) {
122
+ debug('No git remote configured or failed to get remote URL', error);
123
+ }
124
+ return {
125
+ repository,
126
+ branch,
127
+ commit,
128
+ commit_message: commitMessage,
129
+ author,
130
+ dirty: status.length > 0,
131
+ staged_files: stagedFiles.length > 0 ? stagedFiles : undefined,
132
+ modified_files: modifiedFiles.length > 0 ? modifiedFiles : undefined,
133
+ };
134
+ }
135
+ catch (error) {
136
+ debug('Failed to collect git context', error);
137
+ return undefined;
138
+ }
139
+ }
140
+ /**
141
+ * Detects which AI coding tool might have been used
142
+ */
143
+ export async function detectAITool(workspaceRoot) {
144
+ const indicators = [];
145
+ let name = 'unknown';
146
+ let confidence = 'low';
147
+ // Check for Cursor
148
+ if (existsSync(join(workspaceRoot, '.cursor'))) {
149
+ indicators.push('.cursor directory present');
150
+ name = 'cursor';
151
+ confidence = 'high';
152
+ }
153
+ // Check for Copilot
154
+ if (existsSync(join(workspaceRoot, '.github', 'copilot'))) {
155
+ indicators.push('.github/copilot directory present');
156
+ name = 'copilot';
157
+ confidence = 'medium';
158
+ }
159
+ // Check VS Code settings for AI tools
160
+ const vscodePath = join(workspaceRoot, '.vscode', 'settings.json');
161
+ if (existsSync(vscodePath)) {
162
+ try {
163
+ const settings = readFileSync(vscodePath, 'utf-8');
164
+ if (settings.includes('github.copilot')) {
165
+ indicators.push('VS Code Copilot settings found');
166
+ if (name === 'unknown') {
167
+ name = 'copilot';
168
+ confidence = 'medium';
169
+ }
170
+ }
171
+ if (settings.includes('cursor')) {
172
+ indicators.push('VS Code Cursor settings found');
173
+ if (name === 'unknown') {
174
+ name = 'cursor';
175
+ confidence = 'medium';
176
+ }
177
+ }
178
+ }
179
+ catch (error) {
180
+ debug('Failed to parse VS Code settings.json for AI tool detection', error);
181
+ }
182
+ }
183
+ // Check for Claude Code (CLAUDE.md)
184
+ if (existsSync(join(workspaceRoot, 'CLAUDE.md'))) {
185
+ indicators.push('CLAUDE.md present');
186
+ if (name === 'unknown') {
187
+ name = 'claude-code';
188
+ confidence = 'high';
189
+ }
190
+ }
191
+ // Check environment variables
192
+ if (process.env.CURSOR_SESSION) {
193
+ indicators.push('CURSOR_SESSION env var present');
194
+ name = 'cursor';
195
+ confidence = 'high';
196
+ }
197
+ if (process.env.GITHUB_COPILOT_TOKEN) {
198
+ indicators.push('GITHUB_COPILOT_TOKEN env var present');
199
+ name = 'copilot';
200
+ confidence = 'high';
201
+ }
202
+ // Check recent git commits for AI indicators
203
+ try {
204
+ const { stdout } = await execFileAsync('git', ['log', '-5', '--format=%s'], {
205
+ cwd: workspaceRoot,
206
+ });
207
+ const messages = stdout.toLowerCase();
208
+ if (messages.includes('generated by cursor') || messages.includes('cursor:')) {
209
+ indicators.push('Recent commit mentions Cursor');
210
+ name = 'cursor';
211
+ confidence = 'medium';
212
+ }
213
+ if (messages.includes('copilot') || messages.includes('generated by github')) {
214
+ indicators.push('Recent commit mentions Copilot');
215
+ name = 'copilot';
216
+ confidence = 'medium';
217
+ }
218
+ if (messages.includes('claude') || messages.includes('anthropic')) {
219
+ indicators.push('Recent commit mentions Claude');
220
+ name = 'claude-code';
221
+ confidence = 'medium';
222
+ }
223
+ }
224
+ catch (error) {
225
+ debug('Git log command failed while detecting AI tool from commits', error);
226
+ }
227
+ // If no indicators found, return undefined
228
+ if (indicators.length === 0) {
229
+ return undefined;
230
+ }
231
+ return {
232
+ name,
233
+ confidence,
234
+ indicators,
235
+ };
236
+ }
237
+ /**
238
+ * Converts gate results to check summaries
239
+ */
240
+ export function summariseChecks(results) {
241
+ return results.checks.map((check) => ({
242
+ name: check.check,
243
+ passed: check.passed,
244
+ score: check.score,
245
+ issues_count: check.details?.findings
246
+ ? check.details.findings.length
247
+ : undefined,
248
+ }));
249
+ }
250
+ /**
251
+ * Creates a full provenance record from a gate run
252
+ */
253
+ export async function createProvenanceRecord(params) {
254
+ const { workspaceRoot, filesChecked, scope, results, trigger, startTime, planId, parentId } = params;
255
+ const endTime = Date.now();
256
+ // Collect all context in parallel
257
+ const [environment, gitContext, aiTool] = await Promise.all([
258
+ collectEnvironment(workspaceRoot),
259
+ collectGitContext(workspaceRoot),
260
+ detectAITool(workspaceRoot),
261
+ ]);
262
+ // Get current user
263
+ let user;
264
+ try {
265
+ const { stdout } = await execFileAsync('git', ['config', 'user.name']);
266
+ user = stdout.trim() || undefined;
267
+ }
268
+ catch (error) {
269
+ debug('Failed to get git user.name, falling back to env vars', error);
270
+ user = process.env.USER || process.env.USERNAME;
271
+ }
272
+ return {
273
+ id: generateProvenanceId(),
274
+ timestamp: new Date().toISOString(),
275
+ scope,
276
+ files_checked: filesChecked.map((f) => f.replace(workspaceRoot, '').replace(/^\//, '')),
277
+ files_count: filesChecked.length,
278
+ overall_passed: results.overall,
279
+ overall_score: results.score,
280
+ checks: summariseChecks(results),
281
+ environment,
282
+ git: gitContext,
283
+ ai_tool: aiTool,
284
+ plan_id: planId,
285
+ parent_id: parentId,
286
+ trigger,
287
+ duration_ms: endTime - startTime,
288
+ user,
289
+ };
290
+ }
291
+ /**
292
+ * Formats a provenance record for display
293
+ */
294
+ export function formatProvenanceRecord(record) {
295
+ const lines = [];
296
+ lines.push(`Provenance Record: ${record.id}`);
297
+ lines.push(`${'─'.repeat(50)}`);
298
+ lines.push(`Timestamp: ${record.timestamp}`);
299
+ lines.push(`Duration: ${record.duration_ms}ms`);
300
+ lines.push(`Trigger: ${record.trigger}`);
301
+ lines.push('');
302
+ lines.push('Scope:');
303
+ lines.push(` Type: ${record.scope}`);
304
+ lines.push(` Files: ${record.files_count}`);
305
+ lines.push('');
306
+ lines.push('Results:');
307
+ lines.push(` Overall: ${record.overall_passed ? '✓ PASSED' : '✗ FAILED'}`);
308
+ lines.push(` Score: ${record.overall_score}/100`);
309
+ lines.push(' Checks:');
310
+ for (const check of record.checks) {
311
+ const status = check.passed ? '✓' : '✗';
312
+ const score = check.score !== undefined ? ` (${check.score})` : '';
313
+ lines.push(` ${status} ${check.name}${score}`);
314
+ }
315
+ lines.push('');
316
+ if (record.git) {
317
+ lines.push('Git Context:');
318
+ if (record.git.branch)
319
+ lines.push(` Branch: ${record.git.branch}`);
320
+ if (record.git.commit)
321
+ lines.push(` Commit: ${record.git.commit.substring(0, 8)}`);
322
+ if (record.git.dirty)
323
+ lines.push(` Status: dirty (uncommitted changes)`);
324
+ lines.push('');
325
+ }
326
+ if (record.ai_tool) {
327
+ lines.push('AI Tool Detected:');
328
+ lines.push(` Tool: ${record.ai_tool.name}`);
329
+ lines.push(` Confidence: ${record.ai_tool.confidence}`);
330
+ if (record.ai_tool.indicators) {
331
+ lines.push(` Indicators: ${record.ai_tool.indicators.join(', ')}`);
332
+ }
333
+ lines.push('');
334
+ }
335
+ lines.push('Environment:');
336
+ lines.push(` OS: ${record.environment.os}`);
337
+ lines.push(` Node: ${record.environment.node_version}`);
338
+ lines.push(` Anvil: ${record.environment.anvil_version}`);
339
+ if (record.environment.ci) {
340
+ lines.push(` CI: ${record.environment.ci_provider || 'yes'}`);
341
+ }
342
+ return lines.join('\n');
343
+ }
344
+ /**
345
+ * Create an AuthorshipLog from provenance context
346
+ *
347
+ * This bridges the Anvil provenance system with the Git AI Standard v3.0.0,
348
+ * enabling AI-generated code to be tracked in Git Notes.
349
+ *
350
+ * @param params - Parameters for creating the authorship log
351
+ * @param params.commitSha - Full 40-character commit SHA (use `git rev-parse` to resolve short refs)
352
+ * @returns AuthorshipLog if AI tool is detected, null otherwise
353
+ * @throws Error if commitSha is not a valid 40-character hex SHA
354
+ */
355
+ export function createAuthorshipLog(params) {
356
+ const { commitSha, fileLineMap, messages, humanAuthor, totalAdditions = 0, totalDeletions = 0, } = params;
357
+ // Validate commit SHA is a full 40-character hex string
358
+ if (!/^[a-f0-9]{40}$/.test(commitSha)) {
359
+ throw new Error(`commitSha must be a full 40-character hex SHA, got: "${commitSha}". ` +
360
+ 'Use `git rev-parse <ref>` to resolve short refs or branch names.');
361
+ }
362
+ // Try to detect the current AI agent
363
+ const agent = detectCurrentAgent();
364
+ if (!agent) {
365
+ debug('No AI agent detected, skipping authorship log creation');
366
+ return null;
367
+ }
368
+ const sessionHash = generateSessionHash(agent.tool, agent.id);
369
+ // Build attestations from file→line map
370
+ const attestations = {};
371
+ for (const [file, ranges] of Object.entries(fileLineMap)) {
372
+ attestations[file] = [{ sessionHash, lineRanges: ranges }];
373
+ }
374
+ // Build prompt record
375
+ const promptRecord = {
376
+ agent_id: agent,
377
+ messages: messages.map((m) => ({
378
+ type: m.type,
379
+ text: m.text,
380
+ timestamp: new Date().toISOString(),
381
+ })),
382
+ total_additions: totalAdditions,
383
+ total_deletions: totalDeletions,
384
+ accepted_lines: totalAdditions, // Assume all lines accepted initially
385
+ overridden_lines: 0,
386
+ human_author: humanAuthor,
387
+ };
388
+ return {
389
+ attestations,
390
+ metadata: {
391
+ schema_version: SCHEMA_VERSION,
392
+ base_commit_sha: commitSha,
393
+ prompts: {
394
+ [sessionHash]: promptRecord,
395
+ },
396
+ },
397
+ };
398
+ }
399
+ /**
400
+ * Attach an AuthorshipLog to a commit via Git Notes
401
+ *
402
+ * @param log - The authorship log to attach
403
+ * @param workspaceRoot - The repository root directory
404
+ */
405
+ export async function attachAuthorshipToCommit(log, workspaceRoot) {
406
+ const commitSha = log.metadata.base_commit_sha;
407
+ await writeAuthorshipNote(commitSha, log, workspaceRoot);
408
+ debug(`Attached authorship log to commit ${commitSha.slice(0, 8)}`);
409
+ }
410
+ /**
411
+ * Create and attach an AuthorshipLog in one operation
412
+ *
413
+ * Convenience function that combines createAuthorshipLog and attachAuthorshipToCommit.
414
+ *
415
+ * @param params - Parameters for creating the authorship log
416
+ * @param workspaceRoot - The repository root directory
417
+ * @returns true if log was created and attached, false if no AI agent detected
418
+ */
419
+ export async function recordAIAuthorship(params, workspaceRoot) {
420
+ const log = createAuthorshipLog(params);
421
+ if (!log)
422
+ return false;
423
+ await attachAuthorshipToCommit(log, workspaceRoot);
424
+ return true;
425
+ }
@@ -0,0 +1,85 @@
1
+ import type { AuthorshipLog } from './types.js';
2
+ /**
3
+ * Git Notes namespace for AI authorship logs
4
+ * Per Git AI Standard v3.0.0
5
+ */
6
+ export declare const NOTES_REF = "refs/notes/ai";
7
+ /**
8
+ * Write an authorship log to Git Notes for a commit
9
+ *
10
+ * @param commitSha - The commit SHA to attach the note to
11
+ * @param log - The authorship log to write
12
+ * @param workspaceRoot - The repository root directory
13
+ */
14
+ export declare function writeAuthorshipNote(commitSha: string, log: AuthorshipLog, workspaceRoot: string): Promise<void>;
15
+ /**
16
+ * Read an authorship log from Git Notes for a commit
17
+ *
18
+ * @param commitSha - The commit SHA to read the note from (or 'HEAD')
19
+ * @param workspaceRoot - The repository root directory
20
+ * @returns The parsed AuthorshipLog, or null if no note exists
21
+ */
22
+ export declare function readAuthorshipNote(commitSha: string, workspaceRoot: string): Promise<AuthorshipLog | null>;
23
+ /**
24
+ * List all commits with authorship notes
25
+ *
26
+ * @param workspaceRoot - The repository root directory
27
+ * @returns Array of commit SHAs that have authorship notes
28
+ */
29
+ export declare function listAuthorshipNotes(workspaceRoot: string): Promise<string[]>;
30
+ /**
31
+ * Remove an authorship note from a commit
32
+ *
33
+ * @param commitSha - The commit SHA to remove the note from
34
+ * @param workspaceRoot - The repository root directory
35
+ * @returns true if the note was removed, false if it didn't exist
36
+ */
37
+ export declare function removeAuthorshipNote(commitSha: string, workspaceRoot: string): Promise<boolean>;
38
+ /**
39
+ * Copy authorship note when rebasing (from old SHA to new SHA)
40
+ *
41
+ * Updates the base_commit_sha in metadata to point to the new commit.
42
+ *
43
+ * @param fromSha - The original commit SHA
44
+ * @param toSha - The new commit SHA after rebase
45
+ * @param workspaceRoot - The repository root directory
46
+ * @returns true if the note was copied, false if source note didn't exist
47
+ */
48
+ export declare function copyAuthorshipNote(fromSha: string, toSha: string, workspaceRoot: string): Promise<boolean>;
49
+ /**
50
+ * Push authorship notes to remote
51
+ *
52
+ * @param remote - Remote name (e.g., 'origin')
53
+ * @param workspaceRoot - The repository root directory
54
+ */
55
+ export declare function pushAuthorshipNotes(remote: string, workspaceRoot: string): Promise<void>;
56
+ /**
57
+ * Fetch authorship notes from remote
58
+ *
59
+ * @param remote - Remote name (e.g., 'origin')
60
+ * @param workspaceRoot - The repository root directory
61
+ */
62
+ export declare function fetchAuthorshipNotes(remote: string, workspaceRoot: string): Promise<void>;
63
+ /**
64
+ * Check if a commit has an authorship note
65
+ *
66
+ * @param commitSha - The commit SHA to check
67
+ * @param workspaceRoot - The repository root directory
68
+ * @returns true if the commit has an authorship note
69
+ */
70
+ export declare function hasAuthorshipNote(commitSha: string, workspaceRoot: string): Promise<boolean>;
71
+ /**
72
+ * Get summary statistics of AI authorship in a range of commits
73
+ *
74
+ * @param range - Git revision range (e.g., 'main..HEAD', 'HEAD~10..HEAD')
75
+ * @param workspaceRoot - The repository root directory
76
+ * @returns Summary object with counts
77
+ */
78
+ export declare function getAuthorshipStats(range: string, workspaceRoot: string): Promise<{
79
+ totalCommits: number;
80
+ commitsWithAI: number;
81
+ totalAdditions: number;
82
+ totalDeletions: number;
83
+ tools: Record<string, number>;
84
+ }>;
85
+ //# sourceMappingURL=git-notes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-notes.d.ts","sourceRoot":"","sources":["../../../src/provenance/git-ai-standard/git-notes.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AA8BhD;;;GAGG;AACH,eAAO,MAAM,SAAS,kBAAkB,CAAC;AAEzC;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,aAAa,EAClB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC,CAsCf;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAoB/B;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAiBlF;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,OAAO,CAAC,CAelB;AAED;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,OAAO,CAAC,CA+BlB;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAc9F;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAc/F;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,OAAO,CAAC,CAalB;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC;IACT,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/B,CAAC,CA+CD"}