faf-cli 6.12.0 → 6.14.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.
- package/README.md +28 -4
- package/dist/cli.js +249 -246
- package/dist/cli.js.map +12 -9
- package/dist/core/interview.d.ts +36 -9
- package/dist/core/loop.d.ts +91 -0
- package/dist/detect/dart.d.ts +29 -0
- package/dist/detect/relentless.d.ts +34 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +135 -134
- package/dist/index.js.map +10 -8
- package/package.json +1 -1
package/dist/core/interview.d.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* Authoring doctrine baked into the prompts: 6Ws answers are terse LABELS
|
|
18
18
|
* (3-4 words, hard cap <6) — a scannable spec card, not prose.
|
|
19
19
|
*/
|
|
20
|
+
import type { SeededContextDetailed } from '../detect/relentless.js';
|
|
20
21
|
/** Bump when questions/options change — consumers can pin or report it. */
|
|
21
22
|
export declare const INTERVIEW_VERSION = "faf-interview/1";
|
|
22
23
|
export interface InterviewOption {
|
|
@@ -24,9 +25,9 @@ export interface InterviewOption {
|
|
|
24
25
|
value: string;
|
|
25
26
|
description: string;
|
|
26
27
|
}
|
|
27
|
-
export interface InterviewQuestion {
|
|
28
|
+
export interface InterviewQuestion<P extends string = string> {
|
|
28
29
|
/** Canonical slot path (core/slots.ts is the spine). */
|
|
29
|
-
path:
|
|
30
|
+
path: P;
|
|
30
31
|
question: string;
|
|
31
32
|
/** Short chip/header label (max ~12 chars) for option-UI consumers. */
|
|
32
33
|
header: string;
|
|
@@ -34,22 +35,37 @@ export interface InterviewQuestion {
|
|
|
34
35
|
required: boolean;
|
|
35
36
|
options?: InterviewOption[];
|
|
36
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* The human/sourced boundary, as TYPES — the load-bearing line in FAF.
|
|
40
|
+
*
|
|
41
|
+
* HumanSlotPath = the 8 things only a human knows (name + goal + the 6Ws).
|
|
42
|
+
* SourcedSlotPath = the slots detection fills (language + the stack).
|
|
43
|
+
*
|
|
44
|
+
* They are DISJOINT by construction. Typing the two interviews against these
|
|
45
|
+
* makes it a COMPILE error to put a sourced slot in the human interview (or
|
|
46
|
+
* vice-versa) — the "Interview-16" drift becomes unrepresentable at the Truth,
|
|
47
|
+
* not merely caught by a downstream test. "I'll remember" is not a fix; a type
|
|
48
|
+
* is. (claude-faf-mcp's wjttc-faf-go-boundary test is the belt; this is the
|
|
49
|
+
* braces — the source itself can no longer drift.)
|
|
50
|
+
*/
|
|
51
|
+
export type HumanSlotPath = 'project.name' | 'project.goal' | 'human_context.who' | 'human_context.what' | 'human_context.why' | 'human_context.where' | 'human_context.when' | 'human_context.how';
|
|
52
|
+
export type SourcedSlotPath = 'project.main_language' | 'stack.frontend' | 'stack.backend' | 'stack.database' | 'stack.runtime' | 'stack.hosting' | 'stack.build' | 'stack.cicd';
|
|
37
53
|
/**
|
|
38
54
|
* THE 8-Q 6Ws INTERVIEW — the core. Project identity (name + goal) plus the
|
|
39
55
|
* six Ws. Language is deliberately NOT here: detection finds it; humans are
|
|
40
56
|
* only asked what machines cannot derive. The 6Ws are the underivable half.
|
|
41
57
|
*/
|
|
42
|
-
export declare const SIX_WS_INTERVIEW: InterviewQuestion[];
|
|
58
|
+
export declare const SIX_WS_INTERVIEW: InterviewQuestion<HumanSlotPath>[];
|
|
43
59
|
/**
|
|
44
60
|
* Stack interview — asked only for slots ACTIVE for the app_type and still
|
|
45
61
|
* empty. Selects where a common vocabulary exists; every select includes
|
|
46
62
|
* Other (specify) and None where absence is legitimate.
|
|
47
63
|
*/
|
|
48
|
-
export declare const STACK_INTERVIEW: InterviewQuestion[];
|
|
64
|
+
export declare const STACK_INTERVIEW: InterviewQuestion<SourcedSlotPath>[];
|
|
49
65
|
/** The full ordered registry: the 8-Q core first, then stack. */
|
|
50
66
|
export declare const INTERVIEW: InterviewQuestion[];
|
|
51
67
|
/** Lookup by slot path. */
|
|
52
|
-
export declare const INTERVIEW_BY_PATH: Map<string, InterviewQuestion
|
|
68
|
+
export declare const INTERVIEW_BY_PATH: Map<string, InterviewQuestion<string>>;
|
|
53
69
|
/**
|
|
54
70
|
* Plain-object companion to INTERVIEW_BY_PATH. A Map JSON-serializes to `{}`,
|
|
55
71
|
* which reads as "the export shipped empty" to any consumer that crosses a
|
|
@@ -90,6 +106,12 @@ export interface TableOf8Row {
|
|
|
90
106
|
value: string;
|
|
91
107
|
status: BoxStatus;
|
|
92
108
|
seeded: boolean;
|
|
109
|
+
/** Provenance for a SEEDED row — where the suggestion was sourced from, so the
|
|
110
|
+
* human confirms informed: 'project goal' or a README locus ('README:## Why').
|
|
111
|
+
* Absent on filled/empty rows. */
|
|
112
|
+
source?: string;
|
|
113
|
+
/** 0..1 confidence for a seeded row, by source quality. Absent otherwise. */
|
|
114
|
+
confidence?: number;
|
|
93
115
|
}
|
|
94
116
|
export interface TableOf8 {
|
|
95
117
|
version: string;
|
|
@@ -100,11 +122,16 @@ export interface TableOf8 {
|
|
|
100
122
|
complete: boolean;
|
|
101
123
|
}
|
|
102
124
|
/**
|
|
103
|
-
* Build the Table-of-8 from a .faf object (any/empty) + an optional goal
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
125
|
+
* Build the Table-of-8 from a .faf object (any/empty) + an optional goal and an
|
|
126
|
+
* optional sourced README extraction (relentlessContextDetailed). The goal seeds
|
|
127
|
+
* WHO/WHAT/WHERE via seedSixWsFromGoal (facts only, source 'project goal'); the
|
|
128
|
+
* detailed README form covers WHY/WHEN/HOW and any slot the goal didn't reach,
|
|
129
|
+
* each carrying its own provenance. Rows the .faf already fills are 'filled';
|
|
130
|
+
* seeded ones carry `source`+`confidence` so the human confirms informed; the
|
|
131
|
+
* rest are 'empty' (ask the human). Goal wins over README where both have a
|
|
132
|
+
* fact (the deliberate sentence beats the extraction). Pure.
|
|
107
133
|
*/
|
|
108
134
|
export declare function buildTableOf8(faf: Record<string, unknown>, opts?: {
|
|
109
135
|
goal?: string;
|
|
136
|
+
detailed?: SeededContextDetailed;
|
|
110
137
|
}): TableOf8;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The faf_loop decision core — the brain of "100% or ask human".
|
|
3
|
+
*
|
|
4
|
+
* A pure, snapshot decision: given a parsed .faf, classify its remaining gaps
|
|
5
|
+
* as SOURCEABLE (detection can fill — language, stack: no human needed) or
|
|
6
|
+
* HUMAN (only the human knows — the goal + the 6Ws), and return a verdict:
|
|
7
|
+
*
|
|
8
|
+
* done — nothing left (or already 100%). Stop, success.
|
|
9
|
+
* can-source — sourceable gaps remain → the loop runs auto and re-scores.
|
|
10
|
+
* needs-human — ONLY human gaps remain → the wall; ask (or seed-and-confirm).
|
|
11
|
+
*
|
|
12
|
+
* The classification IS the human/sourced boundary at runtime: a slot is human
|
|
13
|
+
* iff its slot category is 'human' (the 6Ws) or it's the goal sentence. The
|
|
14
|
+
* loop sources everything it can FIRST (minimise human asks); only when the
|
|
15
|
+
* sourceable well is dry does it turn to the human. It never invents a human
|
|
16
|
+
* slot to reach 100% — needs-human is a legitimate, honest terminal, not a
|
|
17
|
+
* failure.
|
|
18
|
+
*
|
|
19
|
+
* This is the deterministic engine the CLI `faf loop` and the agentic
|
|
20
|
+
* `/faf-loop` skill both decide from. Orchestration (run auto, re-score, the
|
|
21
|
+
* no-progress + iteration-cap guards) lives in the command on top of this.
|
|
22
|
+
*/
|
|
23
|
+
export type LoopStatus = 'done' | 'can-source' | 'needs-human';
|
|
24
|
+
export interface LoopGaps {
|
|
25
|
+
/** Empty active slots only the human can give — the goal + the 6Ws. */
|
|
26
|
+
human: string[];
|
|
27
|
+
/** Empty active slots detection can fill — language + the stack. */
|
|
28
|
+
sourceable: string[];
|
|
29
|
+
}
|
|
30
|
+
export interface LoopVerdict {
|
|
31
|
+
status: LoopStatus;
|
|
32
|
+
score: number;
|
|
33
|
+
gaps: LoopGaps;
|
|
34
|
+
/** The questions to put to the human — populated only when status is
|
|
35
|
+
* 'needs-human' (sourceable work is done; the human is all that's left). */
|
|
36
|
+
ask: Array<{
|
|
37
|
+
path: string;
|
|
38
|
+
question: string;
|
|
39
|
+
}>;
|
|
40
|
+
}
|
|
41
|
+
/** Default empty test: null/undefined, blank, or a placeholder token. The
|
|
42
|
+
* What-Not (`slotignored`) is handled separately — it is never a gap. */
|
|
43
|
+
export declare function isEmptyValue(value: unknown): boolean;
|
|
44
|
+
/** A path is HUMAN (only the human can give it) iff it is the goal sentence or
|
|
45
|
+
* its slot category is 'human' (the 6Ws). Everything else — name, language,
|
|
46
|
+
* the stack — is sourceable by detection. */
|
|
47
|
+
export declare function isHumanSlot(path: string): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Classify the empty, non-ignored interviewable slots of a .faf into human vs
|
|
50
|
+
* sourceable. Scope = the interviewable slots (the 6Ws + name + goal + the core
|
|
51
|
+
* stack); `slotignored` (the What-Not) is never a gap.
|
|
52
|
+
*/
|
|
53
|
+
export declare function classifyGaps(data: Record<string, unknown>, isEmpty?: (value: unknown) => boolean): LoopGaps;
|
|
54
|
+
/**
|
|
55
|
+
* The loop verdict for a .faf snapshot. `score` is the current AI-readiness
|
|
56
|
+
* score (the loop's termination test). Precedence is deliberate: source
|
|
57
|
+
* everything possible BEFORE asking the human — so 'can-source' wins while any
|
|
58
|
+
* sourceable gap remains, and 'needs-human' fires only once they're exhausted.
|
|
59
|
+
*/
|
|
60
|
+
export declare function loopVerdict(score: number, data: Record<string, unknown>, isEmpty?: (value: unknown) => boolean): LoopVerdict;
|
|
61
|
+
export type LoopRunStatus = 'done' | 'needs-human' | 'stuck' | 'capped' | 'no-faf';
|
|
62
|
+
export interface LoopDeps {
|
|
63
|
+
/** The current .faf as {data, yaml}, or null when none exists. */
|
|
64
|
+
read(): {
|
|
65
|
+
data: Record<string, unknown>;
|
|
66
|
+
yaml: string;
|
|
67
|
+
} | null;
|
|
68
|
+
/** Score raw .faf yaml → 0..100 (the termination test). */
|
|
69
|
+
score(yaml: string): number;
|
|
70
|
+
/** Source what detection can (auto): fill the stack and WRITE the .faf. */
|
|
71
|
+
runAuto(): void;
|
|
72
|
+
}
|
|
73
|
+
export interface LoopRunOptions {
|
|
74
|
+
/** Hard cap on auto rounds (default 5). */
|
|
75
|
+
maxRounds?: number;
|
|
76
|
+
isEmpty?: (value: unknown) => boolean;
|
|
77
|
+
}
|
|
78
|
+
export interface LoopRunResult {
|
|
79
|
+
status: LoopRunStatus;
|
|
80
|
+
score: number;
|
|
81
|
+
/** Auto rounds actually run. */
|
|
82
|
+
rounds: number;
|
|
83
|
+
/** Score after each read — the climb, for narration. */
|
|
84
|
+
history: number[];
|
|
85
|
+
/** The human questions to put — populated only when status is 'needs-human'. */
|
|
86
|
+
ask: Array<{
|
|
87
|
+
path: string;
|
|
88
|
+
question: string;
|
|
89
|
+
}>;
|
|
90
|
+
}
|
|
91
|
+
export declare function runLoop(deps: LoopDeps, opts?: LoopRunOptions): LoopRunResult;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dart/Flutter detection — CONTENT-AWARE pubspec classification.
|
|
3
|
+
*
|
|
4
|
+
* A pubspec.yaml alone does NOT mean Flutter. The same manifest backs Flutter
|
|
5
|
+
* apps, pure-Dart CLIs, packages, servers (Dart Frog / Shelf / Serverpod), and
|
|
6
|
+
* MCP servers (dart_mcp / mcp_server). We read the dependencies and branch —
|
|
7
|
+
* collapsing everything to "Flutter" is the bug this module fixes.
|
|
8
|
+
*
|
|
9
|
+
* Single source of truth for Dart: imported by both the v6 scanner (app-type +
|
|
10
|
+
* language) and Turbo-Cat (stack fills), so the two engines agree by construction.
|
|
11
|
+
*/
|
|
12
|
+
export type DartAppType = 'mobile' | 'mcp' | 'backend' | 'cli' | 'library';
|
|
13
|
+
export interface DartProject {
|
|
14
|
+
/** faf app_type — Flutter→mobile, Dart MCP→mcp, Dart server→backend, CLI→cli, else library. */
|
|
15
|
+
appType: DartAppType;
|
|
16
|
+
isFlutter: boolean;
|
|
17
|
+
/** Primary framework for the stack (Flutter / Dart Frog / Serverpod / Shelf / …) or ''. */
|
|
18
|
+
framework: string;
|
|
19
|
+
/** Flutter state management (Riverpod / Bloc / Provider / GetX / MobX / Signals) or ''. */
|
|
20
|
+
stateManagement: string;
|
|
21
|
+
/** Routing (go_router / auto_route) or ''. */
|
|
22
|
+
routing: string;
|
|
23
|
+
/** Test framework (flutter_test / test) or ''. */
|
|
24
|
+
testing: string;
|
|
25
|
+
/** Human-readable rationale for the .faf `# found:` comment (Glass Hood). */
|
|
26
|
+
found: string;
|
|
27
|
+
}
|
|
28
|
+
/** Classify a Dart/Flutter project from its pubspec.yaml. Returns null if not Dart. */
|
|
29
|
+
export declare function detectDartProject(dir: string): DartProject | null;
|
|
@@ -19,4 +19,38 @@ export interface SeededContext {
|
|
|
19
19
|
when?: string;
|
|
20
20
|
how?: string;
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* A sourced 6-W value WITH its provenance — where it came from and how much to
|
|
24
|
+
* trust it. The honesty gate: a fill the human can audit before confirming.
|
|
25
|
+
* - `source`: the artifact + locus it was relocated from, e.g.
|
|
26
|
+
* 'package.json:description' or 'README:## Why'. If we can't name a source,
|
|
27
|
+
* we don't emit a value — sourced-or-empty, never invented.
|
|
28
|
+
* - `confidence`: 0..1 by source quality (structured field > named section >
|
|
29
|
+
* heuristic match). A signal for the seeded/confirm UI, not a score.
|
|
30
|
+
*/
|
|
31
|
+
export interface SourcedValue {
|
|
32
|
+
value: string;
|
|
33
|
+
source: string;
|
|
34
|
+
confidence: number;
|
|
35
|
+
}
|
|
36
|
+
export interface SeededContextDetailed {
|
|
37
|
+
who?: SourcedValue;
|
|
38
|
+
what?: SourcedValue;
|
|
39
|
+
why?: SourcedValue;
|
|
40
|
+
where?: SourcedValue;
|
|
41
|
+
when?: SourcedValue;
|
|
42
|
+
how?: SourcedValue;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The 6-W extractor WITH provenance — each slot carries {value, source,
|
|
46
|
+
* confidence}. This is the auditable form: the seeded/confirm UI can show the
|
|
47
|
+
* human WHERE a value was relocated from before they accept it.
|
|
48
|
+
*/
|
|
49
|
+
export declare function relentlessContextDetailed(dir: string): SeededContextDetailed;
|
|
50
|
+
/**
|
|
51
|
+
* The bare 6-W extractor — values only, backward-compatible. A pure projection
|
|
52
|
+
* of `relentlessContextDetailed` (single internal source; identical output to
|
|
53
|
+
* the pre-provenance version). Existing consumers (claude-faf-mcp's faf_auto)
|
|
54
|
+
* keep working unchanged; opt into provenance via the Detailed form.
|
|
55
|
+
*/
|
|
22
56
|
export declare function relentlessContext(dir: string): SeededContext;
|
package/dist/index.d.ts
CHANGED
|
@@ -8,13 +8,15 @@ export { findFafFile, readFaf, readFafRaw } from './interop/faf.js';
|
|
|
8
8
|
export { generateProjectHtml, writeProjectHtml } from './interop/projecthtml.js';
|
|
9
9
|
export { generateServerCard, writeServerCard, fafContextBlock, registryMeta, registryName, REGISTRY_PUBLISHER_KEY, } from './interop/servercard.js';
|
|
10
10
|
export type { ServerCardOptions } from './interop/servercard.js';
|
|
11
|
-
export type { InterviewQuestion, InterviewOption, GoalSeed, TableOf8, TableOf8Row, BoxStatus } from './core/interview.js';
|
|
11
|
+
export type { InterviewQuestion, InterviewOption, HumanSlotPath, SourcedSlotPath, GoalSeed, TableOf8, TableOf8Row, BoxStatus } from './core/interview.js';
|
|
12
12
|
export { INTERVIEW, SIX_WS_INTERVIEW, STACK_INTERVIEW, INTERVIEW_BY_PATH, INTERVIEW_PATHS, INTERVIEW_VERSION, questionForSlot, interviewForMissing, seedSixWsFromGoal, buildTableOf8, } from './core/interview.js';
|
|
13
|
+
export type { LoopStatus, LoopGaps, LoopVerdict, LoopRunStatus, LoopDeps, LoopRunOptions, LoopRunResult } from './core/loop.js';
|
|
14
|
+
export { classifyGaps, loopVerdict, isHumanSlot, isEmptyValue, runLoop } from './core/loop.js';
|
|
13
15
|
export { BENCH_VERSION, deriveQuestionSet, publicQuestions, gradeAnswers, buildReceipt, normalizeAnswer, answersMatch, ALIAS_GROUPS, } from './commands/bench.js';
|
|
14
16
|
export type { BenchQuestion, QuestionSet, GradeResult, BenchState, RunRecord } from './commands/bench.js';
|
|
15
17
|
export { turboCatScan, turboCatSlots } from './detect/turbo-cat.js';
|
|
16
18
|
export type { TurboCatResult, DiscoveredFormat } from './detect/turbo-cat.js';
|
|
17
|
-
export { relentlessContext } from './detect/relentless.js';
|
|
18
|
-
export type { SeededContext } from './detect/relentless.js';
|
|
19
|
+
export { relentlessContext, relentlessContextDetailed } from './detect/relentless.js';
|
|
20
|
+
export type { SeededContext, SeededContextDetailed, SourcedValue } from './detect/relentless.js';
|
|
19
21
|
export { assembleFreshFaf } from './detect/assemble.js';
|
|
20
22
|
export * as kernel from './wasm/kernel.js';
|