@agjs/tsforge 0.5.1 → 0.5.2

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/detect-gate.ts +26 -4
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agjs/tsforge",
3
3
  "type": "module",
4
- "version": "0.5.1",
4
+ "version": "0.5.2",
5
5
  "license": "MIT",
6
6
  "description": "TypeScript coding harness with a deterministic gate, stack-aware guardrails, and stream-level correction.",
7
7
  "repository": {
@@ -142,6 +142,11 @@ const STRICT_TSCONFIG_OVERLAY = `{
142
142
  /** The gate overlay's home: tsforge's cache dir + the overlay filename. */
143
143
  const GATE_TSCONFIG_DIR = ".tsforge";
144
144
  const GATE_TSCONFIG_FILE = "tsconfig.gate.json";
145
+ /** Persistent incremental-typecheck cache (in .tsforge/, git-ignored). Reused
146
+ * across settles so a warm `tsc` only re-checks what changed — tsc stays the
147
+ * authority, just amortized. */
148
+ const GATE_TSBUILDINFO_FILE = "gate.tsbuildinfo";
149
+ const INCREMENTAL_FLAGS = `--incremental --tsBuildInfoFile ${GATE_TSCONFIG_DIR}/${GATE_TSBUILDINFO_FILE}`;
145
150
 
146
151
  // The web-stack scaffolds (Vite + React full-kit, or Vite vanilla) live in the
147
152
  // registry; this module just lays them down and builds their gate. shadcn/TanStack
@@ -780,7 +785,7 @@ async function tscPart(cwd: string): Promise<string | null> {
780
785
  );
781
786
  await ignoreGateArtifact(cwd);
782
787
 
783
- return `"${TSC_BIN}" --noEmit -p ${GATE_TSCONFIG_DIR}/${GATE_TSCONFIG_FILE}`;
788
+ return `"${TSC_BIN}" --noEmit ${INCREMENTAL_FLAGS} -p ${GATE_TSCONFIG_DIR}/${GATE_TSCONFIG_FILE}`;
784
789
  }
785
790
 
786
791
  // Greenfield: bring a strict tsconfig so tsc can gate — but only when this is
@@ -788,8 +793,11 @@ async function tscPart(cwd: string): Promise<string | null> {
788
793
  // Unlike the overlay, a greenfield tsconfig.json is a DURABLE project file.
789
794
  if (await Bun.file(join(cwd, "package.json")).exists()) {
790
795
  await Bun.write(join(cwd, "tsconfig.json"), STRICT_TSCONFIG);
796
+ // The buildinfo lives in .tsforge/ (git-ignored), NOT next to the durable
797
+ // tsconfig — so incremental never leaks a cache file into the user's tree.
798
+ await ignoreGateArtifact(cwd);
791
799
 
792
- return `"${TSC_BIN}" --noEmit -p tsconfig.json`;
800
+ return `"${TSC_BIN}" --noEmit ${INCREMENTAL_FLAGS} -p tsconfig.json`;
793
801
  }
794
802
 
795
803
  return null;
@@ -801,12 +809,26 @@ async function tscPart(cwd: string): Promise<string | null> {
801
809
  * that intentionally tracks rules.json) is never clobbered. */
802
810
  async function ignoreGateArtifact(cwd: string): Promise<void> {
803
811
  const ignore = join(cwd, GATE_TSCONFIG_DIR, ".gitignore");
812
+ const entries = [GATE_TSCONFIG_FILE, GATE_TSBUILDINFO_FILE];
813
+ const file = Bun.file(ignore);
814
+
815
+ if (!(await file.exists())) {
816
+ await Bun.write(ignore, `${entries.join("\n")}\n`);
804
817
 
805
- if (await Bun.file(ignore).exists()) {
806
818
  return;
807
819
  }
808
820
 
809
- await Bun.write(ignore, `${GATE_TSCONFIG_FILE}\n`);
821
+ // Exists (maybe a user's, or an older tsforge one without the buildinfo line):
822
+ // append only the missing entries so we never clobber what's there.
823
+ const current = await file.text();
824
+ const missing = entries.filter((e) => !current.split("\n").includes(e));
825
+
826
+ if (missing.length > 0) {
827
+ await Bun.write(
828
+ ignore,
829
+ `${current.replace(/\n*$/u, "\n")}${missing.join("\n")}\n`
830
+ );
831
+ }
810
832
  }
811
833
 
812
834
  /** The syntactic idiom layer — ALWAYS tsforge's bundled strict eslint config