@agjs/tsforge 0.3.2 → 0.3.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/package.json +1 -1
- package/scripts/sweep.ts +2 -0
- package/src/config/config.constants.ts +1 -0
- package/src/config/flags.ts +4 -0
- package/src/loop/prompt/index.ts +8 -1
- package/src/loop/prompt/prompt.ts +36 -1
- package/src/loop/run.ts +11 -5
- package/src/stack-detection/detect.ts +15 -0
- package/src/stack-detection/index.ts +1 -1
package/package.json
CHANGED
package/scripts/sweep.ts
CHANGED
|
@@ -87,6 +87,8 @@ function variantToEnvVars(variant: IFeatureVariant): Record<string, string> {
|
|
|
87
87
|
envVars.TSFORGE_HASHLINE = state === "1" ? "1" : "0";
|
|
88
88
|
} else if (dim === "lsp_write_feedback") {
|
|
89
89
|
envVars.TSFORGE_LSP_WRITE_FEEDBACK = state === "1" ? "1" : "0";
|
|
90
|
+
} else if (dim === "simplicity") {
|
|
91
|
+
envVars.TSFORGE_SIMPLICITY = state === "1" ? "1" : "0";
|
|
90
92
|
}
|
|
91
93
|
// else: unknown dimension, skip
|
|
92
94
|
}
|
package/src/config/flags.ts
CHANGED
|
@@ -29,4 +29,8 @@ export const flags = {
|
|
|
29
29
|
* (A/B control, default ON — set to "0" to disable). */
|
|
30
30
|
lspWriteFeedback: (): boolean =>
|
|
31
31
|
process.env.TSFORGE_LSP_WRITE_FEEDBACK !== "0",
|
|
32
|
+
/** Scratch-utility simplicity guidance — appends a "shortest correct solution"
|
|
33
|
+
* block to the build prompt for from-scratch, non-web tasks (A/B control,
|
|
34
|
+
* default OFF until a sweep validates it). */
|
|
35
|
+
simplicity: (): boolean => isOn(ENV_FLAG.simplicity),
|
|
32
36
|
};
|
package/src/loop/prompt/index.ts
CHANGED
|
@@ -1,2 +1,9 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {
|
|
2
|
+
SYSTEM,
|
|
3
|
+
CHAT_SYSTEM,
|
|
4
|
+
COMPACT_SYSTEM,
|
|
5
|
+
SCRATCH_SIMPLICITY_GUIDANCE,
|
|
6
|
+
buildSystemPrompt,
|
|
7
|
+
seedPrompt,
|
|
8
|
+
} from "./prompt";
|
|
2
9
|
export { renderFileSection, exportedSymbols } from "./project-map";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { ITask } from "../../spec";
|
|
2
2
|
import type { IFileView } from "../../lib/fs";
|
|
3
|
-
import { PACK_REGISTRY } from "../../stack-detection";
|
|
3
|
+
import { PACK_REGISTRY, isWebStack } from "../../stack-detection";
|
|
4
4
|
import type { IStackProfile } from "../../stack-detection";
|
|
5
|
+
import { flags } from "../../config";
|
|
5
6
|
import { renderFileSection } from "./project-map";
|
|
6
7
|
|
|
7
8
|
/** The implement-agent system prompt: who it is, the tools, and the strict-TS
|
|
@@ -16,6 +17,40 @@ export const SYSTEM = [
|
|
|
16
17
|
"The gate is `tsc` strict + eslint with every rule an error, so write TypeScript that satisfies it: interfaces are `I`-prefixed; `===`; no `var`; never the non-null `!` — guard index access (`const x = arr[i]; if (x === undefined) {...}`); no `any` and no `as` — type every parameter (e.g. `.reduce((acc: number, r: number) => …, 0)`); explicit boolean conditions. When the gate flags errors in read-only files (tests/types), they come from your editable file being missing or wrong-shaped and vanish once it's correct — don't edit them.",
|
|
17
18
|
].join("\n");
|
|
18
19
|
|
|
20
|
+
/** Appended to SYSTEM for from-scratch, NON-web utility builds when the simplicity
|
|
21
|
+
* flag is on. Pushes the model toward the shortest correct solution — the axis the
|
|
22
|
+
* gate is blind to (it checks correctness, never concision). Carve-outs keep it
|
|
23
|
+
* from fighting the gate's hard rules. NOT for web builds (the views/components
|
|
24
|
+
* architecture legitimately needs many small files). */
|
|
25
|
+
export const SCRATCH_SIMPLICITY_GUIDANCE = [
|
|
26
|
+
"SIMPLICITY — write the SHORTEST correct solution that passes the gate:",
|
|
27
|
+
" • The task's `files:` are the ceiling — do NOT add modules, classes, or",
|
|
28
|
+
" abstractions the task didn't ask for. One focused implementation.",
|
|
29
|
+
" • Prefer built-ins and a direct expression over step-by-step temporaries:",
|
|
30
|
+
" chain the transforms (`xs.filter(...).map(...)`) instead of naming each",
|
|
31
|
+
" intermediate, when it stays readable.",
|
|
32
|
+
" • NO narration/step comments ('// Step 1', '// first we…') — the code is the",
|
|
33
|
+
" explanation. A comment earns its place only for a non-obvious WHY.",
|
|
34
|
+
" • This NEVER overrides the gate: keep `I`-prefixed interfaces, no `as`/`any`/`!`,",
|
|
35
|
+
" real validation at trust boundaries, and any test siblings the gate requires.",
|
|
36
|
+
].join("\n");
|
|
37
|
+
|
|
38
|
+
/** SYSTEM + the simplicity block when it applies, else SYSTEM unchanged. Gated on
|
|
39
|
+
* the `simplicity` flag AND a from-scratch (`!hasExistingCode`) NON-web build —
|
|
40
|
+
* so it never touches existing-repo edits or web/UI apps. */
|
|
41
|
+
export function buildSystemPrompt(
|
|
42
|
+
hasExistingCode: boolean,
|
|
43
|
+
stack: IStackProfile | undefined
|
|
44
|
+
): string {
|
|
45
|
+
const webish = stack !== undefined && isWebStack(stack);
|
|
46
|
+
|
|
47
|
+
if (!flags.simplicity() || hasExistingCode || webish) {
|
|
48
|
+
return SYSTEM;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return `${SYSTEM}\n\n${SCRATCH_SIMPLICITY_GUIDANCE}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
19
54
|
/**
|
|
20
55
|
* The INTERACTIVE assistant prompt (the CLI's `Session`). Unlike `SYSTEM` — which
|
|
21
56
|
* drives a single task to a gate and is told to "keep going until green" — this
|
package/src/loop/run.ts
CHANGED
|
@@ -17,7 +17,7 @@ import type {
|
|
|
17
17
|
} from "./loop.types";
|
|
18
18
|
import { mineLessons, consolidate as consolidateMemory } from "./memory";
|
|
19
19
|
import { flags } from "../config";
|
|
20
|
-
import {
|
|
20
|
+
import { buildSystemPrompt, seedPrompt } from "./prompt";
|
|
21
21
|
import { detectStack } from "../stack-detection";
|
|
22
22
|
import type { TtsrManager } from "./ttsr";
|
|
23
23
|
import {
|
|
@@ -295,17 +295,23 @@ export async function runTask(
|
|
|
295
295
|
|
|
296
296
|
const editable = await readFiles(cwd, task.files);
|
|
297
297
|
const context = await readFiles(cwd, task.context ?? []);
|
|
298
|
+
|
|
299
|
+
// Existing code to navigate? (editable files already have content). Only then
|
|
300
|
+
// do the LSP nav tools earn their decision-surface cost — see toolsFor(). Also
|
|
301
|
+
// gates the scratch-simplicity guidance (from-scratch builds only).
|
|
302
|
+
const hasExistingCode = editable.some((f) => f.content.trim().length > 0);
|
|
303
|
+
|
|
298
304
|
const messages: IChatMessage[] = [
|
|
299
|
-
{
|
|
305
|
+
{
|
|
306
|
+
role: "system",
|
|
307
|
+
content: buildSystemPrompt(hasExistingCode, stackProfile),
|
|
308
|
+
},
|
|
300
309
|
{
|
|
301
310
|
role: "user",
|
|
302
311
|
content: seedPrompt(task, editable, context, stackProfile),
|
|
303
312
|
},
|
|
304
313
|
];
|
|
305
314
|
|
|
306
|
-
// Existing code to navigate? (editable files already have content). Only then
|
|
307
|
-
// do the LSP nav tools earn their decision-surface cost — see toolsFor().
|
|
308
|
-
const hasExistingCode = editable.some((f) => f.content.trim().length > 0);
|
|
309
315
|
const tools = toolsFor(hasExistingCode);
|
|
310
316
|
|
|
311
317
|
// Mode-aware reasoning cap: scratch tasks over-think unbounded, so default
|
|
@@ -9,6 +9,21 @@ import {
|
|
|
9
9
|
type IPackId,
|
|
10
10
|
} from "./packs";
|
|
11
11
|
|
|
12
|
+
/** The pack ids that identify a WEB (browser UI) build. Used to scope behaviours
|
|
13
|
+
* that must NOT apply to web apps (e.g. the scratch-simplicity prompt, whose
|
|
14
|
+
* "shortest solution / no extra files" advice fights the views/components
|
|
15
|
+
* architecture the web scaffold requires). */
|
|
16
|
+
const WEB_PACK_IDS: readonly string[] = [
|
|
17
|
+
"react",
|
|
18
|
+
"react-component-architecture",
|
|
19
|
+
"tanstack-query",
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
/** True when the detected stack is a web/browser UI build. */
|
|
23
|
+
export function isWebStack(profile: IStackProfile): boolean {
|
|
24
|
+
return profile.packs.some((p) => WEB_PACK_IDS.includes(p));
|
|
25
|
+
}
|
|
26
|
+
|
|
12
27
|
/** Parse package.json and extract deps/devDeps, tolerating missing/invalid JSON. */
|
|
13
28
|
async function loadPackageDeps(cwd: string): Promise<{
|
|
14
29
|
deps: Set<string>;
|