faf-cli 7.12.0 → 7.13.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 +39 -25
- package/dist/cli.js +364 -337
- package/dist/cli.js.map +70 -59
- package/dist/core/cwd-guard.d.ts +8 -0
- package/dist/core/faf-dna.d.ts +147 -0
- package/dist/core/faf-source.d.ts +27 -0
- package/dist/core/json-edit.d.ts +77 -0
- package/dist/core/render-hash.d.ts +55 -0
- package/dist/core/safe-write.d.ts +228 -0
- package/dist/core/scorer.d.ts +7 -2
- package/dist/core/shape.d.ts +11 -0
- package/dist/core/slots.d.ts +30 -0
- package/dist/core/typed-none.d.ts +25 -0
- package/dist/core/types.d.ts +8 -0
- package/dist/core/yaml-edit.d.ts +136 -0
- package/dist/detect/assemble.d.ts +49 -5
- package/dist/detect/git-repo.d.ts +50 -0
- package/dist/detect/scanner.d.ts +9 -4
- package/dist/detect/stack.d.ts +14 -1
- package/dist/detect/turbo-cat-knowledge.d.ts +10 -0
- package/dist/detect/turbo-cat.d.ts +6 -2
- package/dist/fafm/index.d.ts +1 -0
- package/dist/fafm/soul.d.ts +108 -6
- package/dist/index.d.ts +24 -3
- package/dist/index.js +203 -163
- package/dist/index.js.map +35 -23
- package/dist/interop/cards.d.ts +52 -3
- package/dist/interop/claude-memory.d.ts +134 -0
- package/dist/interop/claude.d.ts +5 -1
- package/dist/interop/commonmark.d.ts +144 -0
- package/dist/interop/copilot-instructions.d.ts +1 -0
- package/dist/interop/faf.d.ts +123 -11
- package/dist/interop/inject.d.ts +112 -22
- package/dist/interop/projecthtml.d.ts +19 -2
- package/dist/interop/servercard.d.ts +51 -2
- package/dist/wasm/kernel.d.ts +7 -4
- package/package.json +5 -5
- package/project.faf +11 -4
package/dist/core/types.d.ts
CHANGED
|
@@ -60,6 +60,14 @@ export interface ScoreResult {
|
|
|
60
60
|
* `inherited: true` per schema validation.
|
|
61
61
|
*/
|
|
62
62
|
represents?: string;
|
|
63
|
+
/**
|
|
64
|
+
* True when the score is NOT KNOWN: an About Repo with no valid
|
|
65
|
+
* `about.source_score`. `score` is then -1 and `tier` is White only as a
|
|
66
|
+
* placeholder — neither is a result. Render it as "unknown" (—), never as a
|
|
67
|
+
* number or a percentage ("-1/100", "-1%"), and do not seal a receipt or
|
|
68
|
+
* attest it. Set only when true (absent on every calculated or inherited score).
|
|
69
|
+
*/
|
|
70
|
+
unknown?: boolean;
|
|
63
71
|
}
|
|
64
72
|
/** Tier boundary info */
|
|
65
73
|
export interface TierInfo {
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document-preserving YAML edits — how faf changes a .faf or .fafm that is
|
|
3
|
+
* already on disk.
|
|
4
|
+
*
|
|
5
|
+
* The file is parsed with yaml's `parseDocument`, the change is made on the
|
|
6
|
+
* Document through its node APIs (`set` / `setIn` / `delete`), and only the
|
|
7
|
+
* text of the nodes that changed is rewritten. Every other byte stays as it
|
|
8
|
+
* was: comments, blank lines, key order, quoting, scalar source text (`1.10`,
|
|
9
|
+
* `0x1F90`, a 20-digit integer), anchors and aliases, unknown keys, CRLF line
|
|
10
|
+
* ends and a BOM.
|
|
11
|
+
*
|
|
12
|
+
* Safety net: the spliced text is parsed again and must hold the same data as
|
|
13
|
+
* the changed Document and serialise the same way (comments, styles, anchors;
|
|
14
|
+
* blank lines aside). If it does not (a shape the splicer does not handle),
|
|
15
|
+
* faf writes the Document's own serialisation instead — `toString({
|
|
16
|
+
* lineWidth: 0 })`, which still keeps comments, anchors, unknown keys and the
|
|
17
|
+
* source text of every number it did not change. A change that leaves the
|
|
18
|
+
* data as it was returns the original text untouched — even when that
|
|
19
|
+
* serialisation would differ — so callers skip the write.
|
|
20
|
+
*/
|
|
21
|
+
import { Document, YAMLMap, YAMLSeq, type DocumentOptions, type ParseOptions, type SchemaOptions } from 'yaml';
|
|
22
|
+
type YamlOptions = ParseOptions & DocumentOptions & SchemaOptions;
|
|
23
|
+
/** The result of {@link editYaml}. */
|
|
24
|
+
export interface YamlEditResult {
|
|
25
|
+
/** The new text — the original text when nothing changed. */
|
|
26
|
+
text: string;
|
|
27
|
+
/** False when the change left the document as it was. */
|
|
28
|
+
changed: boolean;
|
|
29
|
+
}
|
|
30
|
+
/** Parse options for every document faf edits. */
|
|
31
|
+
export declare const EDIT_OPTIONS: YamlOptions;
|
|
32
|
+
/** Parse `src` for editing; a file that is not valid YAML is refused (an
|
|
33
|
+
* Error whose `cause` is yaml's YAMLParseError, with its line). */
|
|
34
|
+
export declare function parseForEdit(src: string, name: string): Document;
|
|
35
|
+
/**
|
|
36
|
+
* Parse `text`, let `mutate` change the Document, and return the new text with
|
|
37
|
+
* every byte outside the changed nodes kept. `name` labels errors (a file that
|
|
38
|
+
* is not valid YAML is refused — nothing is guessed). When the change leaves
|
|
39
|
+
* the data as it was (the same values, whatever the nodes or their order),
|
|
40
|
+
* the original `text` is returned with `changed: false`.
|
|
41
|
+
*/
|
|
42
|
+
export declare function editYaml(text: string, mutate: (doc: Document) => void, name?: string): YamlEditResult;
|
|
43
|
+
/** {@link editYaml}, also saying how the text was made: `spliced` is true when
|
|
44
|
+
* only the changed nodes were rewritten, false when faf fell back to the
|
|
45
|
+
* Document's own serialisation (or nothing changed). For tests. */
|
|
46
|
+
export declare function editYamlDetailed(text: string, mutate: (doc: Document) => void, name?: string): YamlEditResult & {
|
|
47
|
+
spliced: boolean;
|
|
48
|
+
};
|
|
49
|
+
/** Deep equality of plain parsed values (mappings compared key by key). */
|
|
50
|
+
export declare function sameJs(a: unknown, b: unknown): boolean;
|
|
51
|
+
/** How {@link applyMapData} treats keys the data does not name. */
|
|
52
|
+
export interface ApplyOptions {
|
|
53
|
+
/** Remove keys of a mapping that the data leaves out (or sets `undefined`).
|
|
54
|
+
* Default false: a key the data does not mention stays exactly as it is. */
|
|
55
|
+
prune?: boolean;
|
|
56
|
+
/** Leave every alias (`*name`) as written, whatever the data holds there:
|
|
57
|
+
* it is never expanded or replaced (see {@link keptAliases}). */
|
|
58
|
+
keepAliases?: boolean;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Set `node` to hold `value` with the fewest changes: a mapping is updated key
|
|
62
|
+
* by key, a list item by item (items still there are kept as they are), a
|
|
63
|
+
* scalar in place (its comment, quoting and position kept). Returns the node
|
|
64
|
+
* to store — `node` itself, or a new node when the shape changed.
|
|
65
|
+
*/
|
|
66
|
+
export declare function applyValue(doc: Document, node: unknown, value: unknown, opts?: ApplyOptions): unknown;
|
|
67
|
+
/** Make `map` hold `data`: changed keys updated in place, new keys appended in
|
|
68
|
+
* `data`'s order. Keys `data` leaves out stay, unless `prune` is set. */
|
|
69
|
+
export declare function applyMapData(doc: Document, map: YAMLMap, data: Record<string, unknown>, opts?: ApplyOptions): void;
|
|
70
|
+
/**
|
|
71
|
+
* Merge `data` into `map`, whose parsed value (the file as it was read) is
|
|
72
|
+
* `before`, writing only the paths where `data` differs from `before`:
|
|
73
|
+
* - a key whose value in `data` equals its value in `before` is not touched
|
|
74
|
+
* — its node stays exactly as written, so an alias (`summary: *g`), a
|
|
75
|
+
* merge key or a comment on it survives even when `data` spells out the
|
|
76
|
+
* value the alias read as (a stale copy never replaces a live `*alias`);
|
|
77
|
+
* - a changed mapping is merged key by key the same way;
|
|
78
|
+
* - any other changed value is set with {@link applyValue};
|
|
79
|
+
* - an alias (`stack: *base`) is never replaced or expanded, at any depth:
|
|
80
|
+
* it stays as written, and a change `data` makes under it is not written
|
|
81
|
+
* ({@link keptAliases} lists those paths);
|
|
82
|
+
* - keys `data` leaves out (or sets `undefined`) stay; new keys are appended.
|
|
83
|
+
* This is how writeFaf applies full .faf data to an existing file.
|
|
84
|
+
*/
|
|
85
|
+
export declare function mergeData(doc: Document, map: YAMLMap, before: unknown, data: Record<string, unknown>): void;
|
|
86
|
+
/** An alias faf left as written: where it is (`stack`, `key_files.2`) and
|
|
87
|
+
* what it says (`*base`). With `kind: 'anchor'` the path holds an anchor an
|
|
88
|
+
* alias reads (`alias` then says `&name`): `faf auto` would have filled it,
|
|
89
|
+
* which would change every alias that reads it, so it is left as written. */
|
|
90
|
+
export interface KeptAlias {
|
|
91
|
+
path: string;
|
|
92
|
+
alias: string;
|
|
93
|
+
kind?: 'alias' | 'anchor';
|
|
94
|
+
}
|
|
95
|
+
/** The aliases in `doc` where `data` changed the value the file held
|
|
96
|
+
* (`before`) and the alias does not read as the new value — a change under
|
|
97
|
+
* an alias that faf did not write, because it never replaces or expands an
|
|
98
|
+
* alias. A value `data` only repeats (a stale copy of what the alias read
|
|
99
|
+
* as) is not a change. */
|
|
100
|
+
export declare function keptAliases(doc: Document, data: unknown, before: unknown): KeptAlias[];
|
|
101
|
+
/**
|
|
102
|
+
* Put back every alias of `before` that `after` (the same Document, changed)
|
|
103
|
+
* holds something else in place of: faf never replaces an alias, so that
|
|
104
|
+
* path keeps the file's `*name` and its change is not written. A key the
|
|
105
|
+
* change removed stays removed. Returns the aliases put back.
|
|
106
|
+
*/
|
|
107
|
+
export declare function restoreAliases(before: Document, after: Document): KeptAlias[];
|
|
108
|
+
/**
|
|
109
|
+
* Put back every node of `before` that carries an anchor an alias reads
|
|
110
|
+
* (`frontend: &x None` while `ui_library: *x`) and that `after` (the same
|
|
111
|
+
* Document, changed) holds something else in place of: a fill there would
|
|
112
|
+
* change the value of every alias that reads it, so faf leaves the node as
|
|
113
|
+
* written and its change is not written. Returns those paths (`kind:
|
|
114
|
+
* 'anchor'`, `alias` saying `&name`).
|
|
115
|
+
*/
|
|
116
|
+
export declare function restoreAnchors(before: Document, after: Document): KeptAlias[];
|
|
117
|
+
/**
|
|
118
|
+
* Apply the change from `before` to `after` to `node`, touching nothing the
|
|
119
|
+
* change does not name: for mappings, only keys added, removed or changed
|
|
120
|
+
* between the two are written — a key in the file that neither side mentions
|
|
121
|
+
* stays as it is. Returns the node to store.
|
|
122
|
+
*/
|
|
123
|
+
export declare function applyChange(doc: Document, node: unknown, before: unknown, after: unknown): unknown;
|
|
124
|
+
/** The mapping at `path` (created when absent or empty). Throws when something
|
|
125
|
+
* else is there — faf never replaces a scalar or a list to make room. */
|
|
126
|
+
export declare function mapAt(doc: Document, path: readonly string[], name: string): YAMLMap;
|
|
127
|
+
/**
|
|
128
|
+
* Apply the change `before` → `after` at `path` (see {@link applyChange}).
|
|
129
|
+
* The parent mapping is created when absent; `after === undefined` removes the
|
|
130
|
+
* key. Nothing is touched when the two are equal.
|
|
131
|
+
*/
|
|
132
|
+
export declare function changeAt(doc: Document, path: readonly string[], before: unknown, after: unknown, name: string): void;
|
|
133
|
+
/** The list at `path`, created (as an empty list) when absent or empty.
|
|
134
|
+
* Throws when something else is there. */
|
|
135
|
+
export declare function seqAt(doc: Document, path: readonly string[], name: string): YAMLSeq;
|
|
136
|
+
export {};
|
|
@@ -15,11 +15,55 @@ export declare function assembleFreshFaf(dir: string): Record<string, unknown>;
|
|
|
15
15
|
* consumers (faf-mcp's faf_auto) compose it instead of re-deriving it and
|
|
16
16
|
* drifting (they used to merge assembleFreshFaf's slotignore'd output over the
|
|
17
17
|
* existing file, losing interrogated facts such as a docker-compose Redis).
|
|
18
|
+
*
|
|
19
|
+
* Shape: `existing` must be a mapping (an empty document, null, reads as `{}`);
|
|
20
|
+
* a scalar or a list throws rather than being spread into character keys. A
|
|
21
|
+
* non-null scalar `project:` (older writers stored `project: <name>`) is lifted
|
|
22
|
+
* to `{ name: String(value) }`, so the name is kept and the rest can be filled.
|
|
23
|
+
*
|
|
24
|
+
* Words typed into a slot — a typed none (`None` / `N/A` / `not applicable`)
|
|
25
|
+
* or any other placeholder word (`unknown`, `"null"`), any case — are an
|
|
26
|
+
* empty slot: they score 0 until filled.
|
|
27
|
+
* - In a tech slot (every slot but the 6Ws) a repo fact fills it: "if it's
|
|
28
|
+
* a fact, fill the slot". With no fact the words stay exactly as the file
|
|
29
|
+
* has them, comment included; faf never writes `''` over them.
|
|
30
|
+
* - In a tech slot the file's app-type (`project.type`) leaves out, the
|
|
31
|
+
* app-type's decision is the fact: with no repo fact, the slot becomes
|
|
32
|
+
* `slotignored` (shown as N/A) in place of the words, an empty value or a
|
|
33
|
+
* placeholder. A real value there is kept. `slotignored` comes only from
|
|
34
|
+
* the app-type — in a slot the app-type uses, it is never written, even
|
|
35
|
+
* when detection reads the repo as another type; such a slot gets the
|
|
36
|
+
* repo's fact for it instead (`runtime: Go` from go.mod), or keeps what
|
|
37
|
+
* it had. A `project.type` faf does not know decides nothing, and neither
|
|
38
|
+
* does the `library` detection falls back to when the repo has no
|
|
39
|
+
* classifying signal.
|
|
40
|
+
* - In a 6W (`human_context.*`) the words are the person's: auto never
|
|
41
|
+
* replaces them (`faf go` asks).
|
|
42
|
+
* A slot that says `slotignored` under either of its names (`stack.db` for
|
|
43
|
+
* `stack.database`) gets no detected value under either. A slot the file
|
|
44
|
+
* names only by its Mk4 name (`stack.db`) keeps that name: faf adds no
|
|
45
|
+
* on-wire twin (`stack.database`), and a repo fact fills the name the file
|
|
46
|
+
* uses.
|
|
47
|
+
*
|
|
48
|
+
* The result is marked as a fill: writeFaf leaves a node with an anchor that
|
|
49
|
+
* an alias reads as written (`frontend: &x None` with `ui_library: *x`) —
|
|
50
|
+
* filling it would change every alias — and reports it like a kept alias.
|
|
18
51
|
*/
|
|
19
52
|
export declare function updateExistingFaf(dir: string, existing: Record<string, unknown>): Record<string, unknown>;
|
|
20
|
-
/** Fill empty
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
53
|
+
/** Fill empty slots in `target` with values from `source`. `target` wins
|
|
54
|
+
* when its slot is non-empty. Empty here is '', null, undefined, an empty
|
|
55
|
+
* list or mapping — this is what lets interrogated/detected values overwrite
|
|
56
|
+
* the empty-string defaults that detectStack writes to human_context.
|
|
57
|
+
*
|
|
58
|
+
* Typed words — a typed none (`None`, `N/A`, `not applicable`) or any other
|
|
59
|
+
* placeholder word (`unknown`, `"null"`), any case — are an empty slot, but
|
|
60
|
+
* only a fact replaces them: in a tech slot (every slot but the 6Ws, under
|
|
61
|
+
* either of its names) a source value that is real content — not empty, not
|
|
62
|
+
* a placeholder, not `slotignored` — fills it. Anything else leaves the
|
|
63
|
+
* words exactly as they are: no fact, a 6W (`human_context.*`, the
|
|
64
|
+
* person's), or a place that is not a slot.
|
|
65
|
+
*
|
|
66
|
+
* A `_meta` the target already carries (the user's own) is never filled
|
|
67
|
+
* over: faf's runtime `_meta` from `source` is merged only into a target
|
|
68
|
+
* without one. */
|
|
25
69
|
export declare function fillEmpties(target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pure parts of `faf git` — no git, no network, no child process.
|
|
3
|
+
*
|
|
4
|
+
* `faf git <url>` validates the URL, clones the repo into a temp folder, and
|
|
5
|
+
* authors a fresh .faf from it with the same pipeline `faf auto` runs. The
|
|
6
|
+
* clone is the only step that runs git, and it stays in the CLI command. The
|
|
7
|
+
* rest lives here so consumers (claude-faf-mcp's faf_git, faf-mcp) compose it:
|
|
8
|
+
* they fetch the repo their own way, then call `authorFafFromRepo(dir)` and get
|
|
9
|
+
* the same .faf `faf git` would write — canonical keys, scored by faf's own
|
|
10
|
+
* scorer — instead of a hand-rolled extractor with its own schema.
|
|
11
|
+
*/
|
|
12
|
+
import type { FafData } from '../core/types.js';
|
|
13
|
+
/**
|
|
14
|
+
* Validate + normalize a GitHub repo reference into a safe clone URL.
|
|
15
|
+
*
|
|
16
|
+
* A `faf git` URL is an untrusted CLI/MCP argument. This gate rejects any
|
|
17
|
+
* shell metacharacter or whitespace, and only ever returns a URL built from a
|
|
18
|
+
* strict allowlist pattern — defense in depth alongside the no-shell
|
|
19
|
+
* `execFileSync` clone (which already makes argument injection structurally
|
|
20
|
+
* impossible). Nothing carrying a metacharacter or control char ever reaches
|
|
21
|
+
* `git`: metachars are caught here; anything else fails the allowlist below.
|
|
22
|
+
*
|
|
23
|
+
* Accepts:
|
|
24
|
+
* owner/repo · github.com/owner/repo · https://github.com/owner/repo
|
|
25
|
+
* (optional `.git` suffix, optional trailing slash; full http(s) URLs to
|
|
26
|
+
* any host pass through with a single `.git` suffix)
|
|
27
|
+
*
|
|
28
|
+
* Throws on empty / malformed / unsafe input.
|
|
29
|
+
*/
|
|
30
|
+
export declare function normalizeGitUrl(input: string): string;
|
|
31
|
+
/** The repo name from a normalized clone URL: .../owner/repo.git → repo. */
|
|
32
|
+
export declare function repoNameFromUrl(repoUrl: string): string;
|
|
33
|
+
export interface AuthorFafFromRepoOptions {
|
|
34
|
+
/** The repo's URL (a clone URL, or any form `normalizeGitUrl` accepts).
|
|
35
|
+
* When the fetched folder gives the project no name of its own (no
|
|
36
|
+
* package.json name), the project is named after the repo instead of the
|
|
37
|
+
* folder it was fetched into. */
|
|
38
|
+
repoUrl?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Author a fresh .faf, as data, from a repo that is already on disk — the
|
|
42
|
+
* function form of `faf git`. Pass the folder you cloned or unpacked the repo
|
|
43
|
+
* into. Runs the full `faf auto` pipeline (detect → interrogate → slotignore →
|
|
44
|
+
* Turbo-Cat → Relentless) on that folder only, and returns the FafData —
|
|
45
|
+
* nothing is written, nothing is fetched, no git is run.
|
|
46
|
+
*
|
|
47
|
+
* Write it with `writeFaf` and score the written text with `scoreFafYaml`, so
|
|
48
|
+
* the score you report is the one faf reports.
|
|
49
|
+
*/
|
|
50
|
+
export declare function authorFafFromRepo(dir: string, opts?: AuthorFafFromRepoOptions): FafData;
|
package/dist/detect/scanner.d.ts
CHANGED
|
@@ -49,7 +49,8 @@ export declare function detectPolyglotLanguage(dir: string): string | null;
|
|
|
49
49
|
export declare function readPackageJson(dir: string): PackageJson | null;
|
|
50
50
|
/** Detect frameworks in a directory */
|
|
51
51
|
export declare function detectFrameworks(dir: string): DetectedFramework[];
|
|
52
|
-
/** Detect the primary language of a project
|
|
52
|
+
/** Detect the primary language of a project from its manifests — `''` when
|
|
53
|
+
* there is no evidence. */
|
|
53
54
|
export declare function detectLanguage(dir: string): string;
|
|
54
55
|
/** Project-type detection result + rationale (the #found list).
|
|
55
56
|
* Each `found` entry is a human-readable signal that contributed to the
|
|
@@ -72,13 +73,17 @@ export declare function detectProjectTypeWithRationale(dir: string): ProjectType
|
|
|
72
73
|
* returns just the type string. New code should prefer
|
|
73
74
|
* `detectProjectTypeWithRationale` to access the `#found` rationale. */
|
|
74
75
|
export declare function detectProjectType(dir: string): string;
|
|
75
|
-
/** Detect the runtime
|
|
76
|
+
/** Detect the runtime from a runtime file or manifest — `''` when there is
|
|
77
|
+
* no evidence. */
|
|
76
78
|
export declare function detectRuntime(dir: string): string;
|
|
77
|
-
/** Detect package manager
|
|
79
|
+
/** Detect the package manager from repo facts — a manifest that implies one,
|
|
80
|
+
* package.json's `packageManager` field, then a lockfile. `''` when there is
|
|
81
|
+
* no evidence: a package.json alone does not say which manager installs it. */
|
|
78
82
|
export declare function detectPackageManager(dir: string): string;
|
|
79
83
|
/** Detect CI/CD */
|
|
80
84
|
export declare function detectCicd(dir: string): string | null;
|
|
81
|
-
/** Detect hosting platform
|
|
85
|
+
/** Detect hosting platform — from a platform's own config file. A bare
|
|
86
|
+
* Dockerfile is not one: it shows a container build, not where the app runs. */
|
|
82
87
|
export declare function detectHosting(dir: string): string | null;
|
|
83
88
|
/** Detect SvelteKit adapter from svelte.config.js */
|
|
84
89
|
export declare function detectSvelteAdapter(dir: string): string | null;
|
package/dist/detect/stack.d.ts
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
1
|
import type { FafData } from '../core/types.js';
|
|
2
|
-
/**
|
|
2
|
+
/** Detect the project stack from repo facts and build .faf data. A slot with
|
|
3
|
+
* no evidence is left empty (`''`) — never a default. */
|
|
3
4
|
export declare function detectStack(dir: string): FafData;
|
|
5
|
+
/**
|
|
6
|
+
* {@link detectStack}'s data, and `facts`: the repo's fact for every stack
|
|
7
|
+
* slot (by field — `runtime`, `database`), whatever the app-type — `''` when
|
|
8
|
+
* the repo shows none. detectStack marks a slot its detected type leaves out
|
|
9
|
+
* `slotignored`; `faf auto` fills such a slot from `facts` when the file's own
|
|
10
|
+
* type uses it (a `backend` project whose repo reads as a CLI still gets
|
|
11
|
+
* `runtime: Go` from go.mod).
|
|
12
|
+
*/
|
|
13
|
+
export declare function detectStackWithFacts(dir: string): {
|
|
14
|
+
data: FafData;
|
|
15
|
+
facts: Record<string, string>;
|
|
16
|
+
};
|
|
@@ -28,6 +28,16 @@
|
|
|
28
28
|
* 😽 We are the FORMAT FREAKS - Quality over Quantity!
|
|
29
29
|
* 🏆 199 formats - CHAMPIONSHIP GRADE FORMAT DETECTION
|
|
30
30
|
*/
|
|
31
|
+
/**
|
|
32
|
+
* A slot value here must name what the file itself proves, in a slot that
|
|
33
|
+
* value fills: `vercel.json` → hosting Vercel; `Dockerfile` → no hosting (it
|
|
34
|
+
* shows a container build, not where the app runs); `jest.config.js` →
|
|
35
|
+
* nothing for CI/CD (Jest is a test runner, not a pipeline). No generic words ('Cloud',
|
|
36
|
+
* 'Containerized', 'API Server'), no language in a framework or backend slot,
|
|
37
|
+
* and no `runtime: Node.js` from a framework config alone — package.json is
|
|
38
|
+
* that fact, and detectRuntime reads it. `faf auto` writes these values into
|
|
39
|
+
* project.faf, so a value that is not a fact about the repo is a guess.
|
|
40
|
+
*/
|
|
31
41
|
export interface FormatKnowledge {
|
|
32
42
|
frameworks: string[];
|
|
33
43
|
slots: Partial<ContextSlots>;
|
|
@@ -4,8 +4,12 @@
|
|
|
4
4
|
* manifest interrogation the v6.0 rewrite narrowed to README+Cargo+package.json.
|
|
5
5
|
*
|
|
6
6
|
* v6-native sync port (the v5 engine was async; sync integrates cleanly with
|
|
7
|
-
* `auto`). Two-layer: (1A) config files
|
|
8
|
-
*
|
|
7
|
+
* `auto`). Two-layer: (1A) config files in the project directory itself,
|
|
8
|
+
* (1B) source extensions below it; priority-wins slot recommendations.
|
|
9
|
+
*
|
|
10
|
+
* Boundary: detection never looks above the project directory. A parent's
|
|
11
|
+
* tsconfig.json, Cargo.toml or vercel.json is another project's evidence —
|
|
12
|
+
* a monorepo package, a subfolder or a new folder under ~ must not inherit it.
|
|
9
13
|
*
|
|
10
14
|
* Used as the LOWEST-precedence filler in `auto`: v6's specific detection wins;
|
|
11
15
|
* Turbo-Cat fills only the slots still empty (esp. non-npm stacks).
|
package/dist/fafm/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** .fafm knowledge-profile library (TS) — INTEROP with claude-fafm-sdk 1.0 */
|
|
2
2
|
export { PRIORITY_ORDER, PRIORITY_RANK, KNOWLEDGE_TYPES, type Fact, type Priority, type Profile, type SoulDoc, } from './types.js';
|
|
3
3
|
export { Soul, utcNow, canonicalPriority, factFromObj, factToObj, } from './soul.js';
|
|
4
|
+
export type { RecallOptions } from './soul.js';
|
|
4
5
|
export { fromClaudeDir, DEFAULT_SKIP } from './from-claude-dir.js';
|
package/dist/fafm/soul.d.ts
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Soul — local .fafm model (TS mirror of claude-fafm-sdk 1.0 Soul).
|
|
3
3
|
* INTEROP: load/save fidelity, residual preserve, recall SoT.
|
|
4
|
+
*
|
|
5
|
+
* A soul loaded from a file keeps that file's text. `save` writes only what
|
|
6
|
+
* changed since the load (or the last save) into it — through the YAML
|
|
7
|
+
* Document, so comments, key order, quoting, the `version`, a missing
|
|
8
|
+
* `profile`, a hand-kept `index` and every unknown key stay as they were.
|
|
9
|
+
* Known keys faf does not model in the shape the file has them (a `facts` or
|
|
10
|
+
* `index` written as a mapping, `sessions` as a mapping, …) are kept verbatim;
|
|
11
|
+
* a change that would have to rewrite one is refused, with nothing written.
|
|
4
12
|
*/
|
|
5
13
|
import { type Fact, type SoulDoc } from './types.js';
|
|
6
14
|
export declare function utcNow(): string;
|
|
7
15
|
export declare function canonicalPriority(p: string | null | undefined): string;
|
|
8
16
|
export declare function factFromObj(obj: unknown): Fact;
|
|
9
17
|
export declare function factToObj(f: Fact): unknown;
|
|
18
|
+
/** Filters for {@link Soul.recall}. */
|
|
19
|
+
export interface RecallOptions {
|
|
20
|
+
tags?: string[];
|
|
21
|
+
type?: string;
|
|
22
|
+
minPriority?: string;
|
|
23
|
+
limit?: number | null;
|
|
24
|
+
}
|
|
10
25
|
export declare class Soul {
|
|
11
26
|
namepoint: string;
|
|
12
27
|
profile: string;
|
|
@@ -21,6 +36,20 @@ export declare class Soul {
|
|
|
21
36
|
private _custom;
|
|
22
37
|
private _extra;
|
|
23
38
|
private _memoryExtra;
|
|
39
|
+
private _version;
|
|
40
|
+
/** The file the soul was loaded from or last saved to. Undefined while the
|
|
41
|
+
* soul was made with no file (in memory, not saved yet): its first save
|
|
42
|
+
* then refuses a file already at the path — one that appeared since the
|
|
43
|
+
* caller found none — unless `replace`. */
|
|
44
|
+
private _origin;
|
|
45
|
+
/** Each loaded fact's item position in the file's memory.facts. */
|
|
46
|
+
private _factAt;
|
|
47
|
+
/** The index was faf-derived when the soul was loaded (or made, or last
|
|
48
|
+
* saved) — the default save keeps it in step with the facts. */
|
|
49
|
+
private _indexDerived;
|
|
50
|
+
/** The index as it was then: an index changed in memory since is the
|
|
51
|
+
* caller's, and the default save leaves it as it is. */
|
|
52
|
+
private _indexAtRecord;
|
|
24
53
|
constructor(namepoint: string, opts?: {
|
|
25
54
|
profile?: string;
|
|
26
55
|
facts?: Fact[];
|
|
@@ -40,18 +69,89 @@ export declare class Soul {
|
|
|
40
69
|
get custom(): Record<string, unknown>;
|
|
41
70
|
get extra(): Record<string, unknown>;
|
|
42
71
|
get memoryExtra(): Record<string, unknown>;
|
|
72
|
+
/**
|
|
73
|
+
* Load a soul. The root must be a mapping (anything else is refused). Known
|
|
74
|
+
* keys in a shape faf does not model — `memory.facts` or `index` written as
|
|
75
|
+
* a mapping, `memory.sessions` as a mapping, `memory.preferences` as a list,
|
|
76
|
+
* `memory` itself as a list — load as empty and stay in the file verbatim;
|
|
77
|
+
* so do list items in `memory.facts` that are not facts.
|
|
78
|
+
*/
|
|
43
79
|
static load(path: string): Soul;
|
|
80
|
+
private static fromText;
|
|
81
|
+
/** Note whether the index is faf-derived now (at load, and after a save). */
|
|
82
|
+
private recordIndex;
|
|
83
|
+
/** Take a fact read from item `i` of the file's memory.facts. */
|
|
84
|
+
private adopt;
|
|
44
85
|
static fromFile(path: string): Soul;
|
|
45
86
|
toDoc(): SoulDoc;
|
|
46
87
|
toYaml(): string;
|
|
88
|
+
/** The index faf derives from the facts (INTEROP §5 formula,
|
|
89
|
+
* `${id ?? '?'} — ${text[:width]}` per fact) — without changing the soul. */
|
|
90
|
+
derivedIndex(width?: number): string[];
|
|
91
|
+
/** True when the stored index is exactly the one faf derives from the facts
|
|
92
|
+
* (so faf wrote it, and may rewrite it). A hand-kept index is not. */
|
|
93
|
+
indexIsDerived(width?: number): boolean;
|
|
47
94
|
rebuildIndex(width?: number): string[];
|
|
95
|
+
/**
|
|
96
|
+
* Write the soul — atomically (a failure leaves the original as it was)
|
|
97
|
+
* and never through a link that leaves the folder or dangles.
|
|
98
|
+
*
|
|
99
|
+
* A soul loaded from a file is written as that file's text with only what
|
|
100
|
+
* changed since the load (or the last save) edited into it; a save that
|
|
101
|
+
* changes nothing writes nothing.
|
|
102
|
+
*
|
|
103
|
+
* The index: `reindex: true` rebuilds it from the facts, `reindex: false`
|
|
104
|
+
* keeps it as it is. Left out, the index is rebuilt only when it was
|
|
105
|
+
* faf-derived — {@link indexIsDerived} when the soul was loaded or last
|
|
106
|
+
* saved; a new soul counts as derived unless it was given an index of its
|
|
107
|
+
* own — and has not been changed in memory since. A hand-kept index is
|
|
108
|
+
* never touched.
|
|
109
|
+
* Refused, with nothing written: a change to a known key the file holds in
|
|
110
|
+
* a shape faf does not model (adding a fact to `memory.facts` written as a
|
|
111
|
+
* mapping, say); a save over the file the soul was loaded from (or last
|
|
112
|
+
* saved to) when that file changed on disk since — SafePathError `changed`,
|
|
113
|
+
* so an edit made meanwhile is never written over; and a save of a soul
|
|
114
|
+
* made with no file (or to a path it was not loaded from) when a file is
|
|
115
|
+
* already there — the same `changed` refusal, so a soul.fafm that appeared
|
|
116
|
+
* after the caller found none is kept. `replace: true` is the explicit
|
|
117
|
+
* overwrite of such a file (`faf memory convert --force`).
|
|
118
|
+
*/
|
|
48
119
|
save(path: string, opts?: {
|
|
49
120
|
reindex?: boolean;
|
|
121
|
+
replace?: boolean;
|
|
50
122
|
}): string;
|
|
51
123
|
toFile(path: string, opts?: {
|
|
52
124
|
reindex?: boolean;
|
|
125
|
+
replace?: boolean;
|
|
53
126
|
}): string;
|
|
127
|
+
/** After a save: the written text (at `real`) is the new origin. */
|
|
128
|
+
private settle;
|
|
129
|
+
/** The soul's modelled state as plain values. */
|
|
130
|
+
private state;
|
|
131
|
+
/** Refuse a change that would rewrite a key kept verbatim. */
|
|
132
|
+
private static refuse;
|
|
133
|
+
/** Edit the loaded Document with what changed since `origin.base`; returns
|
|
134
|
+
* each fact's item position in memory.facts afterwards. */
|
|
135
|
+
private applyTo;
|
|
136
|
+
private applyMemory;
|
|
137
|
+
/** Which facts are still at their place in the file, and which are new.
|
|
138
|
+
* Kept facts must be in the file's order, new ones after them. */
|
|
139
|
+
private sortFacts;
|
|
140
|
+
/** Facts: changed facts are edited where they are (only the fields that
|
|
141
|
+
* changed), deleted ones removed, new ones appended. List items that are
|
|
142
|
+
* not facts are never touched. */
|
|
143
|
+
private applyFacts;
|
|
144
|
+
/** Insert or overwrite a Fact by id, preserving every field it carries (its
|
|
145
|
+
* timestamp included) — the INTEROP merge primitive: on an existing id the
|
|
146
|
+
* whole Fact is replaced. `etch` merges instead. */
|
|
54
147
|
add(fact: Fact): Fact;
|
|
148
|
+
/**
|
|
149
|
+
* Write a fact. With an `id` that is already in the soul, the fact is
|
|
150
|
+
* updated in place: the text and timestamp are new, and of the other fields
|
|
151
|
+
* only those passed here change — links, source, tags, type, priority and
|
|
152
|
+
* any extra fields the fact carries are kept (a priority is never lowered
|
|
153
|
+
* unless one is passed). Otherwise the fact is appended.
|
|
154
|
+
*/
|
|
55
155
|
etch(text: string, opts?: {
|
|
56
156
|
id?: string;
|
|
57
157
|
type?: string;
|
|
@@ -60,12 +160,14 @@ export declare class Soul {
|
|
|
60
160
|
links?: string[];
|
|
61
161
|
source?: string;
|
|
62
162
|
}): Fact;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
163
|
+
/**
|
|
164
|
+
* Deterministic recall (INTEROP §6). Call it as `recall(query, filters)` or
|
|
165
|
+
* `recall({ query, ...filters })`. Bare-string facts are facts: their text
|
|
166
|
+
* is matched and returned like any other.
|
|
167
|
+
*/
|
|
168
|
+
recall(query?: string | null | (RecallOptions & {
|
|
169
|
+
query?: string | null;
|
|
170
|
+
}), opts?: RecallOptions): Fact[];
|
|
69
171
|
getFact(id: string): Fact | null;
|
|
70
172
|
deleteFact(id: string): boolean;
|
|
71
173
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
export type { SlotState, SlotCategory, SlotDef, KernelScoreResult, ScoreResult, TierInfo, FafData, FafbInfo, DetectedFramework, FrameworkSignature, Signal, SignalType, } from './core/types.js';
|
|
2
2
|
export { SLOTS, BASE_SLOTS, ENTERPRISE_SLOTS, SLOT_BY_PATH, slotsByCategory, PLACEHOLDERS, isPlaceholder } from './core/slots.js';
|
|
3
|
+
export { EXPLICIT_NONE, SLOTIGNORED, isExplicitNone } from './core/slots.js';
|
|
3
4
|
export { TIERS, getTier, getNextTier } from './core/tiers.js';
|
|
4
5
|
export { FAF_HEX } from './ui/colors.js';
|
|
5
6
|
export { enrichScore, scoreFafYaml } from './core/scorer.js';
|
|
7
|
+
export { scoreText } from './core/scorer.js';
|
|
6
8
|
export { validateFaf } from './core/schema.js';
|
|
7
9
|
export { findFafFile, readFaf, readFafRaw } from './interop/faf.js';
|
|
10
|
+
export { isNonProjectRoot } from './core/cwd-guard.js';
|
|
8
11
|
export { computeDrift } from './core/drift.js';
|
|
9
12
|
export type { DriftReport, DriftTarget, DriftStatus } from './core/drift.js';
|
|
10
13
|
export { renderProjectHtml, generateProjectHtml, writeProjectHtml } from './interop/projecthtml.js';
|
|
11
|
-
export {
|
|
12
|
-
export
|
|
14
|
+
export type { ProjectHtmlWriteOptions } from './interop/projecthtml.js';
|
|
15
|
+
export { buildServerCard, generateServerCard, writeServerCard, fafContextBlock, registryMeta, registryName, registryTitle, REGISTRY_PUBLISHER_KEY, } from './interop/servercard.js';
|
|
16
|
+
export type { ServerCardOptions, CardWriteOptions } from './interop/servercard.js';
|
|
17
|
+
export { patchServerJson } from './interop/servercard.js';
|
|
18
|
+
export type { ServerJsonIdentity } from './interop/servercard.js';
|
|
13
19
|
export { projectCards, readFafa, findFafaFile, buildA2ACard, generateA2ACard, upsertCatalog, A2A_CONTEXT_URI, } from './interop/cards.js';
|
|
14
20
|
export type { FafaDoc, CardTarget, ProjectedCards } from './interop/cards.js';
|
|
15
21
|
export { Soul as FafmSoul, fromClaudeDir, canonicalPriority, utcNow, PRIORITY_ORDER, PRIORITY_RANK, KNOWLEDGE_TYPES, } from './fafm/index.js';
|
|
16
|
-
export type { Fact as FafmFact, SoulDoc, Profile as FafmProfile } from './fafm/index.js';
|
|
22
|
+
export type { Fact as FafmFact, SoulDoc, Profile as FafmProfile, RecallOptions as FafmRecallOptions } from './fafm/index.js';
|
|
17
23
|
export type { InterviewQuestion, InterviewOption, HumanSlotPath, SourcedSlotPath, GoalSeed, TableOf8, TableOf8Row, BoxStatus } from './core/interview.js';
|
|
18
24
|
export { INTERVIEW, SIX_WS_INTERVIEW, STACK_INTERVIEW, INTERVIEW_BY_PATH, INTERVIEW_PATHS, INTERVIEW_VERSION, questionForSlot, interviewForMissing, seedSixWsFromGoal, buildTableOf8, } from './core/interview.js';
|
|
19
25
|
export type { LoopStatus, LoopGaps, LoopVerdict, LoopRunStatus, LoopDeps, LoopRunOptions, LoopRunResult } from './core/loop.js';
|
|
@@ -25,6 +31,10 @@ export type { TurboCatResult, DiscoveredFormat } from './detect/turbo-cat.js';
|
|
|
25
31
|
export { relentlessContext, relentlessContextDetailed } from './detect/relentless.js';
|
|
26
32
|
export type { SeededContext, SeededContextDetailed, SourcedValue } from './detect/relentless.js';
|
|
27
33
|
export { assembleFreshFaf, updateExistingFaf, fillEmpties } from './detect/assemble.js';
|
|
34
|
+
export { normalizeGitUrl, repoNameFromUrl, authorFafFromRepo } from './detect/git-repo.js';
|
|
35
|
+
export type { AuthorFafFromRepoOptions } from './detect/git-repo.js';
|
|
36
|
+
export { FafDNAManager } from './core/faf-dna.js';
|
|
37
|
+
export type { FafDNA, BirthCertificate, VersionEntry, Milestone } from './core/faf-dna.js';
|
|
28
38
|
export { renderAgentsMd, writeAgentsMd } from './interop/agents.js';
|
|
29
39
|
export { renderGeminiMd, writeGeminiMd } from './interop/gemini.js';
|
|
30
40
|
export { renderCursorrules, writeCursorrules } from './interop/cursorrules.js';
|
|
@@ -32,6 +42,17 @@ export { renderCopilotInstructions, writeCopilotInstructions } from './interop/c
|
|
|
32
42
|
export { renderClaudeMd, writeClaudeMd, readClaudeMd, parseClaudeMd, fafMetaTag } from './interop/claude.js';
|
|
33
43
|
export type { FafMetaOpts } from './interop/claude.js';
|
|
34
44
|
export { injectFafBlock, findFafBlock, FAF_START, FAF_END } from './interop/inject.js';
|
|
45
|
+
export type { InjectOptions } from './interop/inject.js';
|
|
46
|
+
export { legacyStampNote, legacyStampNoteAt } from './interop/inject.js';
|
|
47
|
+
export { claudeProjectId, claudeProjectRoot, resolveClaudeMemoryDir, resolveClaudeMemoryPath, renderClaudeMemory, writeClaudeMemory, claudeMemoryStatus, } from './interop/claude-memory.js';
|
|
48
|
+
export type { ClaudeMemoryOptions, ClaudeMemoryResult, ClaudeMemoryAction, ClaudeMemoryStatus } from './interop/claude-memory.js';
|
|
49
|
+
export { resolveInside, safeWriteFile, SafePathError } from './core/safe-write.js';
|
|
50
|
+
export { readUtf8 } from './core/safe-write.js';
|
|
51
|
+
export type { ResolveInsideOptions, SafeWriteOptions, SafePathReason } from './core/safe-write.js';
|
|
52
|
+
export { safeReplaceOwned, makeDirInside, safeUnlink } from './core/safe-write.js';
|
|
53
|
+
export type { ReplaceOwnedOptions } from './core/safe-write.js';
|
|
35
54
|
export { enrichFromRepo } from './detect/enrich.js';
|
|
36
55
|
export { serializeFaf, writeFaf } from './interop/faf.js';
|
|
56
|
+
export { updateFafFile } from './interop/faf.js';
|
|
57
|
+
export type { UpdateFafResult, WriteFafOptions, KeptAlias } from './interop/faf.js';
|
|
37
58
|
export * as kernel from './wasm/kernel.js';
|