@diffci.com/diffci 0.1.2 → 0.1.3
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/COMMERCIAL.md +35 -0
- package/LICENSE +671 -0
- package/README.md +75 -22
- package/SECURITY.md +61 -0
- package/SUPPORT.md +47 -0
- package/dist-client/src/client/observe.js +6 -5
- package/dist-client/src/planner/test-command.js +25 -1
- package/dist-client/src/repo/adapters/go.js +154 -0
- package/dist-client/src/repo/adapters/index.js +23 -0
- package/dist-client/src/repo/adapters/types.js +3 -0
- package/dist-client/src/repo/adapters/vue.js +63 -0
- package/dist-client/src/repo/graph.js +74 -6
- package/dist-client/src/repo/impact.js +11 -2
- package/dist-client/src/repo/test-discovery.js +6 -0
- package/docs/distribution.md +14 -3
- package/docs/language-support.md +70 -0
- package/package.json +13 -7
|
@@ -3,6 +3,7 @@ import { createTestFileMatcher, DEFAULT_TEST_FILE_MATCHER, testFileMatcherForPro
|
|
|
3
3
|
import { isBuiltin } from "node:module";
|
|
4
4
|
import { dirname, extname, join, normalize, relative, resolve, sep } from "node:path";
|
|
5
5
|
import ts from "typescript";
|
|
6
|
+
import { adapterFiles, REPOSITORY_ADAPTERS } from "./adapters/index.js";
|
|
6
7
|
import { analyzeRepository } from "./analyzer.js";
|
|
7
8
|
const SOURCE_EXTENSIONS = new Set([
|
|
8
9
|
".ts",
|
|
@@ -13,6 +14,7 @@ const SOURCE_EXTENSIONS = new Set([
|
|
|
13
14
|
".cjs",
|
|
14
15
|
".mts",
|
|
15
16
|
".cts",
|
|
17
|
+
".vue",
|
|
16
18
|
]);
|
|
17
19
|
const ASSET_EXTENSIONS = new Set([
|
|
18
20
|
".css",
|
|
@@ -323,7 +325,7 @@ export function discoverNestedTsconfigPaths(repoPath) {
|
|
|
323
325
|
// merged compiler options reproducible across filesystems (readdir order is OS-dependent).
|
|
324
326
|
return found.sort();
|
|
325
327
|
}
|
|
326
|
-
function createProgram(repoPath, fallbackSourceRoots = []) {
|
|
328
|
+
function createProgram(repoPath, fallbackSourceRoots = [], additionalSources = []) {
|
|
327
329
|
// Phase 01 F5 (2026-08-26). This was `ts.findConfigFile(repoPath, ...)`, which starts at repoPath
|
|
328
330
|
// and walks UP - so a repository cloned beneath any directory containing a tsconfig.json was
|
|
329
331
|
// silently analysed against that ANCESTOR's project instead of its own. Flagged as a known latent
|
|
@@ -440,6 +442,10 @@ function createProgram(repoPath, fallbackSourceRoots = []) {
|
|
|
440
442
|
options = { ...options, allowJs: true };
|
|
441
443
|
}
|
|
442
444
|
}
|
|
445
|
+
if (additionalSources.length) {
|
|
446
|
+
fileNames = [...new Set([...fileNames, ...additionalSources])];
|
|
447
|
+
options = { module: ts.ModuleKind.ESNext, moduleResolution: ts.ModuleResolutionKind.Bundler, target: ts.ScriptTarget.ES2022, ...options, allowJs: true };
|
|
448
|
+
}
|
|
443
449
|
const program = ts.createProgram({
|
|
444
450
|
rootNames: fileNames,
|
|
445
451
|
options,
|
|
@@ -523,12 +529,38 @@ export async function buildDependencyGraph(options = {}) {
|
|
|
523
529
|
const start = process.hrtime.bigint();
|
|
524
530
|
const repoPath = options.repoPath ? resolve(options.repoPath) : process.cwd();
|
|
525
531
|
const profile = analyzeRepository(options);
|
|
532
|
+
const files = adapterFiles(repoPath, options.excludeDirs);
|
|
533
|
+
const context = { repoPath, files, profile };
|
|
534
|
+
const contributions = REPOSITORY_ADAPTERS.filter((adapter) => adapter.detect(context)).map((adapter) => adapter.analyze(context));
|
|
535
|
+
const adapterBlockers = contributions.flatMap((item) => item.blockers);
|
|
536
|
+
if (contributions.some((item) => item.id === "go") && files.some((file) => /\.(?:[cm]?[jt]sx?|vue)$/.test(file))) {
|
|
537
|
+
adapterBlockers.push("Mixed Go/JavaScript repositories require explicit cross-language dependencies; full validation required");
|
|
538
|
+
}
|
|
539
|
+
if (contributions.length && files.some((file) => /\.(?:py|rs|java|kt|cs|svelte|astro)$/.test(file))) {
|
|
540
|
+
adapterBlockers.push("Unmodeled languages alongside an adapter require full validation");
|
|
541
|
+
}
|
|
542
|
+
profile.adapters = contributions.map(({ id, version, blockers }) => ({ id, version, blockers }));
|
|
543
|
+
profile.goTestPackages = Object.assign({}, ...contributions.map((item) => item.testPackages));
|
|
544
|
+
profile.goTestEnvironment = contributions.find((item) => item.id === "go")?.executionEnv;
|
|
545
|
+
const adapterTests = contributions.flatMap((item) => item.testFiles);
|
|
546
|
+
profile.testFilePaths = [...new Set([...profile.testFilePaths, ...adapterTests])].sort();
|
|
547
|
+
if (profile.testUniverse) {
|
|
548
|
+
profile.testUniverse.discoveredTestFiles = profile.testFilePaths.length;
|
|
549
|
+
profile.testUniverse.blindSpot = (profile.testUniverse.declaredFrameworks.length > 0 || contributions.some((item) => item.id === "go")) && profile.testFilePaths.length === 0;
|
|
550
|
+
}
|
|
526
551
|
const entryPointPaths = new Set(profile.entryPoints.map((e) => e.path));
|
|
527
|
-
|
|
552
|
+
const vueSources = contributions.some((item) => item.id === "vue")
|
|
553
|
+
? files.filter((file) => /\.[cm]?[jt]sx?$/.test(file)).map((file) => join(repoPath, file)) : [];
|
|
554
|
+
let { program, options: compilerOptions, resolvedViaProjectReferences } = createProgram(repoPath, profile.sourceRoots, vueSources);
|
|
528
555
|
let moduleResolutionCache = ts.createModuleResolutionCache(repoPath, (x) => x, compilerOptions);
|
|
529
556
|
const sourceFiles = program
|
|
530
557
|
.getSourceFiles()
|
|
531
558
|
.filter((sf) => sf.fileName && !sf.fileName.endsWith(".d.ts"));
|
|
559
|
+
for (const item of contributions) {
|
|
560
|
+
for (const virtual of item.virtualSources) {
|
|
561
|
+
sourceFiles.push(ts.createSourceFile(join(repoPath, virtual.path), virtual.source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX));
|
|
562
|
+
}
|
|
563
|
+
}
|
|
532
564
|
const internalSourcePaths = new Set();
|
|
533
565
|
for (const sf of sourceFiles) {
|
|
534
566
|
const rel = toRelativeInternal(repoPath, sf.fileName);
|
|
@@ -536,11 +568,14 @@ export async function buildDependencyGraph(options = {}) {
|
|
|
536
568
|
internalSourcePaths.add(rel);
|
|
537
569
|
}
|
|
538
570
|
}
|
|
539
|
-
const edges =
|
|
571
|
+
const edges = contributions.flatMap((item) => item.edges);
|
|
540
572
|
const unresolved = [];
|
|
541
573
|
const references = [];
|
|
542
|
-
const assetPaths = new Set();
|
|
543
|
-
const
|
|
574
|
+
const assetPaths = new Set(contributions.flatMap((item) => item.assetPaths));
|
|
575
|
+
for (const item of contributions)
|
|
576
|
+
for (const path of item.sourcePaths)
|
|
577
|
+
internalSourcePaths.add(path);
|
|
578
|
+
const edgeKeys = new Set(edges.map((edge) => `${edge.from}|${edge.to}|${edge.kind}`));
|
|
544
579
|
function addEdge(from, to, kind) {
|
|
545
580
|
const key = `${from}|${to}|${kind}`;
|
|
546
581
|
if (edgeKeys.has(key))
|
|
@@ -590,6 +625,20 @@ export async function buildDependencyGraph(options = {}) {
|
|
|
590
625
|
}
|
|
591
626
|
const specifierCategory = classifySpecifier(ref.specifier);
|
|
592
627
|
const resolvableSpecifier = specifierCategory === "relative" || specifierCategory === "absolute" || specifierCategory === "alias" ? stripImportQuery(ref.specifier) : ref.specifier;
|
|
628
|
+
// TypeScript may resolve a Vue import to a declaration shim. Preserve the actual SFC edge.
|
|
629
|
+
if (resolvableSpecifier.endsWith(".vue")) {
|
|
630
|
+
const compilerAliases = Object.entries(compilerOptions.paths ?? {}).map(([pattern, substitutions]) => ({ pattern, substitutions }));
|
|
631
|
+
const candidate = findAssetCandidate(sf.fileName, resolvableSpecifier, compilerAliases.length ? compilerAliases : profile.pathAliases, compilerOptions.baseUrl ?? repoPath);
|
|
632
|
+
const target = candidate && toRelativeInternal(repoPath, candidate);
|
|
633
|
+
if (target && internalSourcePaths.has(target)) {
|
|
634
|
+
addEdge(importerRel, target, ref.kind);
|
|
635
|
+
recordReference("internal-source", importerRel, ref);
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
recordUnresolved(importerRel, ref, "Vue component not found in analyzed source inventory");
|
|
639
|
+
recordReference("unresolved", importerRel, ref);
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
593
642
|
const resolution = ts.resolveModuleName(resolvableSpecifier, sf.fileName, compilerOptions, ts.sys, moduleResolutionCache);
|
|
594
643
|
if (!resolution.resolvedModule || !resolution.resolvedModule.resolvedFileName) {
|
|
595
644
|
const category = classifySpecifier(ref.specifier);
|
|
@@ -637,6 +686,10 @@ export async function buildDependencyGraph(options = {}) {
|
|
|
637
686
|
}
|
|
638
687
|
}
|
|
639
688
|
}
|
|
689
|
+
if (unresolved.some((ref) => ref.importer.endsWith(".vue") || stripImportQuery(ref.specifier).endsWith(".vue"))) {
|
|
690
|
+
adapterBlockers.push("Unresolved Vue dependencies require full validation");
|
|
691
|
+
}
|
|
692
|
+
profile.adapterBlockers = [...adapterBlockers];
|
|
640
693
|
// Nested-package test visibility (2026-08-24, biomejs/biome finding): `internalSourcePaths` above is
|
|
641
694
|
// strictly the TS PROGRAM's own file list (createProgram()'s `include`/nested-tsconfig-merged
|
|
642
695
|
// fileNames) - so a package whose own tsconfig deliberately excludes its test directory (a real,
|
|
@@ -679,7 +732,7 @@ export async function buildDependencyGraph(options = {}) {
|
|
|
679
732
|
profile.stats.testFiles = graph.nodes.filter((n) => n.isTest).length;
|
|
680
733
|
const integrity = validateDependencyGraph(graph);
|
|
681
734
|
const dynamicUnresolvedCount = unresolved.filter((u) => u.dynamic).length;
|
|
682
|
-
const confidence = computeConfidence(unresolved.length, dynamicUnresolvedCount, integrity.criticalCount, profile.stats.sourceFiles, resolvedViaProjectReferences);
|
|
735
|
+
const confidence = adapterBlockers.length ? "UNSAFE" : computeConfidence(unresolved.length, dynamicUnresolvedCount, integrity.criticalCount, profile.stats.sourceFiles, resolvedViaProjectReferences);
|
|
683
736
|
const internalAssetEdges = edges.filter((e) => e.kind === "asset").length;
|
|
684
737
|
const externalReferences = references.filter((r) => r.resolution === "external-package").length;
|
|
685
738
|
const platformBuiltinReferences = references.filter((r) => r.resolution === "platform-builtin").length;
|
|
@@ -700,6 +753,7 @@ export async function buildDependencyGraph(options = {}) {
|
|
|
700
753
|
};
|
|
701
754
|
return {
|
|
702
755
|
graph,
|
|
756
|
+
adapterBlockers,
|
|
703
757
|
profile,
|
|
704
758
|
unresolved,
|
|
705
759
|
references,
|
|
@@ -713,6 +767,18 @@ export async function buildDependencyGraph(options = {}) {
|
|
|
713
767
|
resolvedViaProjectReferences,
|
|
714
768
|
};
|
|
715
769
|
}
|
|
770
|
+
/** Product eligibility includes native adapters; legacy TS corpus classification stays separate. */
|
|
771
|
+
export function classifyRepositoryProject(repoPath) {
|
|
772
|
+
const typescript = classifyTypeScriptProject(repoPath);
|
|
773
|
+
if (typescript.capable)
|
|
774
|
+
return typescript;
|
|
775
|
+
const files = adapterFiles(repoPath);
|
|
776
|
+
if (files.includes("go.mod"))
|
|
777
|
+
return { capable: true, reason: "Go module (package-level analysis)" };
|
|
778
|
+
if (files.some((file) => file.endsWith(".vue")))
|
|
779
|
+
return { capable: true, reason: "Vue single-file components" };
|
|
780
|
+
return { capable: false, reason: "No TypeScript project, Vue components, or root Go module found" };
|
|
781
|
+
}
|
|
716
782
|
/**
|
|
717
783
|
* Can DiffCI build a dependency graph for this repository? (Phase 01 F3, 2026-08-26.)
|
|
718
784
|
*
|
|
@@ -899,6 +965,8 @@ function computeConfidence(unresolvedCount, dynamicUnresolvedCount, integrityCri
|
|
|
899
965
|
* narrowing only applies when unresolved/dynamic-unresolved imports are the actual reason, never when
|
|
900
966
|
* sourceFileCount or integrity triggered it. */
|
|
901
967
|
export function refineConfidenceForDelta(result, changedFiles) {
|
|
968
|
+
if (result.adapterBlockers?.length)
|
|
969
|
+
return "UNSAFE";
|
|
902
970
|
// Anything that was never UNSAFE in the first place passes through untouched - there is nothing to
|
|
903
971
|
// narrow, and re-deriving sourceFileCount/integrity from raw fields here (rather than trusting the
|
|
904
972
|
// already-computed confidence) would be both redundant and a real correctness risk: a caller's
|
|
@@ -3,7 +3,7 @@ import { refineConfidenceForDelta } from "./graph.js";
|
|
|
3
3
|
import { repositoryLayout, UNKNOWN_REPOSITORY_LAYOUT } from "./layout.js";
|
|
4
4
|
import { DEFAULT_TEST_FILE_MATCHER, matchesGlob as matchesTestGlob, testFileMatcherForProfile } from "./test-discovery.js";
|
|
5
5
|
import { resolveTestFixtureOwners } from "./test-fixture-ownership.js";
|
|
6
|
-
const SOURCE_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"]);
|
|
6
|
+
const SOURCE_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts", ".vue", ".go"]);
|
|
7
7
|
const ASSET_EXTENSIONS = new Set([".css", ".scss", ".sass", ".less", ".json", ".jsonc", ".svg", ".png", ".jpg", ".jpeg", ".gif", ".webp", ".ico", ".bmp", ".woff", ".woff2", ".ttf", ".otf", ".eot", ".wasm", ".md", ".txt"]);
|
|
8
8
|
const NEXT_ENTRY_NAMES = new Set(["page", "layout", "route", "api", "loading", "error", "template", "not-found", "middleware", "generatemetadata", "generatestaticparams"]);
|
|
9
9
|
function isSourceFilePath(filePath) { return SOURCE_EXTENSIONS.has(extname(filePath).toLowerCase()); }
|
|
@@ -18,6 +18,10 @@ function isDocumentationFile(filePath, layout) { return layout.isDocumentationPa
|
|
|
18
18
|
function isConfigFile(filePath) {
|
|
19
19
|
const CONFIG_FILE_NAMES = new Set(["package.json", "package-lock.json", "yarn.lock", "pnpm-lock.yaml", "bun.lockb", "bun.lock", "tsconfig.json", "tsconfig.base.json", "tsconfig.build.json", "jsconfig.json"]);
|
|
20
20
|
const base = posix.basename(filePath);
|
|
21
|
+
if (["go.mod", "go.sum", "go.work", "go.work.sum"].includes(base))
|
|
22
|
+
return true;
|
|
23
|
+
if (/^(?:vite|vue|nuxt)\.config\./.test(base))
|
|
24
|
+
return true;
|
|
21
25
|
if (CONFIG_FILE_NAMES.has(base))
|
|
22
26
|
return true;
|
|
23
27
|
if (base.startsWith("next.config"))
|
|
@@ -265,7 +269,12 @@ export class ImpactAnalyzer {
|
|
|
265
269
|
const changedImpacts = delta.files.map((file) => ({ file, category: classifyChangedFile(file, this.isTestFile, this.layout, options.repositoryFiles), reasons: changedFileReasons(file) }));
|
|
266
270
|
const evidence = [];
|
|
267
271
|
const riskSignals = [];
|
|
268
|
-
const fallbackReasons = [];
|
|
272
|
+
const fallbackReasons = [...(graphResult.adapterBlockers ?? [])];
|
|
273
|
+
for (const file of delta.files) {
|
|
274
|
+
if (allChangePaths(file).some((path) => /(?:^|\/)(?:go\.(?:mod|sum|work)|go\.work\.sum|(?:vite|vue|nuxt)\.config\.[^/]+)$/.test(path))) {
|
|
275
|
+
fallbackReasons.push(`Language/framework configuration changed: ${file.path}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
269
278
|
this.applyGlobalRiskRules(delta, riskSignals, fallbackReasons);
|
|
270
279
|
// Stage 1B fix (2026-08-21, docs/research/2026-08-21-stage1b-*.md): refine the graph's raw,
|
|
271
280
|
// delta-independent confidence to THIS delta's changed files - see refineConfidenceForDelta()'s doc
|
|
@@ -370,6 +370,12 @@ export function createTestFileMatcher(patterns, options = {}) {
|
|
|
370
370
|
/** The one place that turns a profile into a matcher, so analyzer discovery, graph node flags and
|
|
371
371
|
* impact classification cannot disagree about what a test is. */
|
|
372
372
|
export function testFileMatcherForProfile(profile) {
|
|
373
|
+
if (profile.goTestPackages && Object.keys(profile.goTestPackages).length) {
|
|
374
|
+
const jsMatcher = testFileMatcherForProfile({ ...profile, goTestPackages: undefined });
|
|
375
|
+
const matcher = ((path) => Object.hasOwn(profile.goTestPackages, path) || jsMatcher(path));
|
|
376
|
+
Object.defineProperty(matcher, "patterns", { value: jsMatcher.patterns });
|
|
377
|
+
return matcher;
|
|
378
|
+
}
|
|
373
379
|
if (!profile.testPatterns)
|
|
374
380
|
return DEFAULT_TEST_FILE_MATCHER;
|
|
375
381
|
return createTestFileMatcher(profile.testPatterns, {
|
package/docs/distribution.md
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
DiffCI has three install surfaces with the same initial contract: observe CI, write a report, and do
|
|
4
4
|
not change what the host repository runs.
|
|
5
5
|
|
|
6
|
+
The product model is open core. The npm CLI and basic GitHub Action are the open-source adoption path;
|
|
7
|
+
DiffCI Cloud adds hosted history, organization dashboards, policies, managed operations, and support.
|
|
8
|
+
See [`open-core-packaging.md`](open-core-packaging.md).
|
|
9
|
+
|
|
6
10
|
## GitHub App
|
|
7
11
|
|
|
8
12
|
The DiffCI Shadow GitHub App is the lowest-friction research and design-partner path. It receives
|
|
@@ -28,7 +32,7 @@ jobs:
|
|
|
28
32
|
- uses: DiffCI/DiffCI.com@v1
|
|
29
33
|
```
|
|
30
34
|
|
|
31
|
-
For the strongest supply-chain posture, pin the Action to a full commit SHA. `npx @diffci.com/diffci verify-workflow`
|
|
35
|
+
For the strongest supply-chain posture, pin the Action to a full commit SHA. `npx @diffci.com/diffci@latest verify-workflow`
|
|
32
36
|
checks that the job is dedicated, read-only, not required by other jobs, and unable to alter the rest
|
|
33
37
|
of CI.
|
|
34
38
|
|
|
@@ -37,8 +41,8 @@ of CI.
|
|
|
37
41
|
The CLI is the standalone npm package surface. The package name is `@diffci.com/diffci`:
|
|
38
42
|
|
|
39
43
|
```bash
|
|
40
|
-
npx @diffci.com/diffci observe
|
|
41
|
-
npx @diffci.com/diffci verify-workflow
|
|
44
|
+
npx @diffci.com/diffci@latest observe
|
|
45
|
+
npx @diffci.com/diffci@latest verify-workflow
|
|
42
46
|
```
|
|
43
47
|
|
|
44
48
|
`observe` writes a JSON report outside the checkout by default. It never runs, skips, cancels, or
|
|
@@ -79,3 +83,10 @@ brew install diffci
|
|
|
79
83
|
|
|
80
84
|
Those should ship only after the npm CLI and Action have signed releases, provenance, pinned build
|
|
81
85
|
workflows, and repeatable package verification.
|
|
86
|
+
|
|
87
|
+
## Supported OSS Channel
|
|
88
|
+
|
|
89
|
+
Tidelift belongs to the open-source package channel. It can provide maintenance, security, license, and
|
|
90
|
+
supply-chain assurance for the npm package without requiring a hosted DiffCI account. It should support
|
|
91
|
+
the OSS core rather than define a separate feature tier. The readiness checklist lives in
|
|
92
|
+
[`tidelift-package-support.md`](tidelift-package-support.md).
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Language and framework support
|
|
2
|
+
|
|
3
|
+
DiffCI now has an extensible repository-adapter boundary and initial Vue and Go support.
|
|
4
|
+
These additions propose selections in the existing observer; they do not enable production CI skipping.
|
|
5
|
+
|
|
6
|
+
| Surface | Implemented scope | Boundaries |
|
|
7
|
+
| --- | --- | --- |
|
|
8
|
+
| JavaScript / TypeScript | Existing dependency analysis and ten existing test-runner command mappings | Runner recognition is not a guarantee of complete framework semantics |
|
|
9
|
+
| Vue | SFC parsing with `@vue/compiler-sfc`; script imports, nested components, compiled template asset imports; propagation into importing JS/TS tests | Runtime component/directive resolution, preprocessors, external/custom SFC blocks, style URLs/imports, Nuxt conventions and glob imports require full validation |
|
|
10
|
+
| Go | One root module; native `go list` metadata; package-level transitive test selection, external test packages, embedded files; `go test -json` commands and result parsing | Workspaces/nested modules, inactive Go files, cgo/native objects, runtime plugins, generation/linkname, external local replacements and incomplete metadata require full validation |
|
|
11
|
+
| Mixed Go and JS/TS | Detected | Full validation until cross-language relationships are declared and modeled |
|
|
12
|
+
| Python, Svelte, Astro, Java/Kotlin, C#, Rust | No new semantic support in this release | Require additional adapters and qualification |
|
|
13
|
+
|
|
14
|
+
## Using the observer
|
|
15
|
+
|
|
16
|
+
Use the existing `observe` command and commit range options. The observer now admits a root
|
|
17
|
+
`go.mod` or Vue SFCs as well as a TypeScript project. Vue projects without a tsconfig have their
|
|
18
|
+
JS/TS files parsed alongside their components. Existing TS-only corpus eligibility remains separate:
|
|
19
|
+
this release does not silently enroll Go repositories in historical JS/TS research cohorts.
|
|
20
|
+
|
|
21
|
+
For Go, install a compatible Go toolchain on the **same host and build environment** used for analysis
|
|
22
|
+
and testing, and prepare the repository's dependencies first using its normal setup procedure.
|
|
23
|
+
DiffCI invokes `go list -mod=readonly -deps -test -json ./...` with `GOTOOLCHAIN=local`, `GOPROXY=off`,
|
|
24
|
+
`GOSUMDB=off`, and `GOWORK=off`. Missing toolchains or dependencies produce refusal/full fallback.
|
|
25
|
+
Go may populate its own build cache but must not modify the checkout. It does not run `go generate`.
|
|
26
|
+
Custom `GOFLAGS` currently require full validation. Discovered GOOS, GOARCH and CGO_ENABLED settings
|
|
27
|
+
are carried into the structured test command.
|
|
28
|
+
|
|
29
|
+
A selection such as `lib/value_test.go` becomes:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
go test -mod=readonly -json -count=1 ./lib
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
It runs the entire package. Consumers must honor the emitted `CommandSpec.env`, preserve the build
|
|
36
|
+
context, and check fallback/command-synthesis status before execution. Commands are not portable
|
|
37
|
+
between different GOOS/GOARCH/GOFLAGS contexts. The controlled runner command policies accept `go`;
|
|
38
|
+
hosted images still need a Go installation before Go workloads can run. No deployment is performed
|
|
39
|
+
by these code changes.
|
|
40
|
+
|
|
41
|
+
Go result parsing counts failing **packages**, records failing test names, and rejects incomplete
|
|
42
|
+
JSON streams. It does not invent test-file counts from package counts, so file-based economics
|
|
43
|
+
qualification may still report an unsupported denominator for Go.
|
|
44
|
+
|
|
45
|
+
## Extension points
|
|
46
|
+
|
|
47
|
+
- `src/repo/adapters/types.ts`: adapter context and graph contribution contract.
|
|
48
|
+
- `src/repo/adapters/index.ts`: framework/language adapter registry and bounded file inventory.
|
|
49
|
+
- `src/repo/adapters/vue.ts`: Vue compiler integration.
|
|
50
|
+
- `src/repo/adapters/go.ts`: native Go package metadata and conservative dependency model.
|
|
51
|
+
- `src/repo/adapters/go-test.ts`: structured Go outcome parsing.
|
|
52
|
+
- `src/planner/test-command.ts`: runner command routing, including verified Go package targets.
|
|
53
|
+
|
|
54
|
+
Adapters contribute source and asset nodes, dependency edges, virtual JS/TS source, runnable test
|
|
55
|
+
identities and explicit blockers. Blockers persist into the profile and graph result and cannot be
|
|
56
|
+
removed by delta-specific reachability refinement. Go graphs bypass the disk cache until every cache
|
|
57
|
+
caller provides a complete toolchain/build-context identity. The cache schema was bumped to prevent
|
|
58
|
+
reuse of graphs created before adapter support.
|
|
59
|
+
|
|
60
|
+
## Validation and qualification
|
|
61
|
+
|
|
62
|
+
`tests/repo/language-adapters.test.ts` covers transitive Vue selection, unsupported-feature fallbacks,
|
|
63
|
+
Go package imports and embeds, metadata failures, command routing, and incomplete output.
|
|
64
|
+
When Go is on PATH, its native integration test runs the observer against a real Git fixture,
|
|
65
|
+
compares successful full/subset test runs, and verifies the subset catches an introduced fault.
|
|
66
|
+
Without Go, that native test is explicitly skipped.
|
|
67
|
+
|
|
68
|
+
These are implementation checks, not prospective customer safety or savings evidence. Each new
|
|
69
|
+
ecosystem still needs real repository shadow observations and economic qualification before broader
|
|
70
|
+
support or savings claims are made.
|
package/package.json
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diffci.com/diffci",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "DiffCI - deterministic change-aware CI planning",
|
|
5
|
+
"license": "AGPL-3.0-only",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/DiffCI/DiffCI.com.git"
|
|
9
|
+
},
|
|
5
10
|
"private": false,
|
|
6
11
|
"type": "module",
|
|
7
12
|
"engines": {
|
|
@@ -18,7 +23,10 @@
|
|
|
18
23
|
"dist-client/src/repo",
|
|
19
24
|
"README.md",
|
|
20
25
|
"docs/distribution.md",
|
|
21
|
-
"docs/language-support.md"
|
|
26
|
+
"docs/language-support.md",
|
|
27
|
+
"SECURITY.md",
|
|
28
|
+
"SUPPORT.md",
|
|
29
|
+
"COMMERCIAL.md"
|
|
22
30
|
],
|
|
23
31
|
"exports": {
|
|
24
32
|
"./*": {
|
|
@@ -90,10 +98,12 @@
|
|
|
90
98
|
"qualify:universe": "tsx scripts/qualify-universe.ts",
|
|
91
99
|
"corpus:register": "tsx scripts/register-corpus-entry.ts",
|
|
92
100
|
"ci:benchmark": "tsx scripts/ci-inference-benchmark.ts",
|
|
93
|
-
"ci:reproduce": "tsx scripts/ci-reproduction.ts"
|
|
101
|
+
"ci:reproduce": "tsx scripts/ci-reproduction.ts",
|
|
102
|
+
"check:oss-boundary": "node scripts/check-oss-boundary.mjs"
|
|
94
103
|
},
|
|
95
104
|
"dependencies": {
|
|
96
105
|
"@types/node": "^22.15.12",
|
|
106
|
+
"@vue/compiler-sfc": "3.5.42",
|
|
97
107
|
"typescript": "^5.8.3",
|
|
98
108
|
"yaml": "^2.9.0"
|
|
99
109
|
},
|
|
@@ -114,9 +124,5 @@
|
|
|
114
124
|
"**/validate-*.test.*",
|
|
115
125
|
"scripts/**/*.test.mjs"
|
|
116
126
|
]
|
|
117
|
-
},
|
|
118
|
-
"repository": {
|
|
119
|
-
"type": "git",
|
|
120
|
-
"url": "https://github.com/DiffCI/DiffCI.com"
|
|
121
127
|
}
|
|
122
128
|
}
|