facult 2.11.0 → 2.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 +215 -688
- package/assets/packs/facult-operating-model/AGENTS.global.md +1 -0
- package/assets/packs/facult-operating-model/instructions/CAPABILITY_COMPOSITION.md +76 -0
- package/assets/packs/facult-operating-model/instructions/EVOLUTION.md +19 -1
- package/assets/packs/facult-operating-model/instructions/LEARNING_AND_WRITEBACK.md +11 -1
- package/docs/README.md +20 -9
- package/docs/assets/fclt-capability-loop.png +0 -0
- package/docs/automations.md +68 -0
- package/docs/built-in-pack.md +12 -4
- package/docs/composable-capability.md +178 -0
- package/docs/concepts.md +13 -1
- package/docs/managed-mode.md +10 -4
- package/docs/project-ai.md +10 -4
- package/docs/reference.md +112 -0
- package/docs/roadmap.md +18 -7
- package/docs/security-trust.md +89 -0
- package/docs/writeback-evolution.md +10 -2
- package/package.json +2 -1
- package/src/builtin-assets.ts +1 -1
- package/src/doctor.ts +307 -5
- package/src/index.ts +6 -0
- package/src/paths-command.ts +223 -0
- package/src/remote-types.ts +18 -2
- package/src/remote.ts +211 -6
package/src/remote.ts
CHANGED
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
type RemoteAgentItem,
|
|
44
44
|
type RemoteIndexItem,
|
|
45
45
|
type RemoteIndexManifest,
|
|
46
|
+
type RemoteInstructionItem,
|
|
46
47
|
type RemoteItemType,
|
|
47
48
|
type RemoteMcpItem,
|
|
48
49
|
type RemoteSkillItem,
|
|
@@ -60,6 +61,8 @@ const QUERY_SPLIT_RE = /\s+/;
|
|
|
60
61
|
const MD_EXT_RE = /\.md$/i;
|
|
61
62
|
const FILE_EXT_RE = /\.[A-Za-z0-9]+$/;
|
|
62
63
|
const TRAILING_SLASH_RE = /\/+$/;
|
|
64
|
+
const LEADING_SLASH_RE = /^\/+/;
|
|
65
|
+
const INSTRUCTION_TITLE_SPLIT_RE = /[-_.\s]+/;
|
|
63
66
|
const PROMPT_PATH_SPLIT_RE = /[,\n]/;
|
|
64
67
|
const GIT_WORKTREE_LINE_RE = /\r?\n/;
|
|
65
68
|
|
|
@@ -222,6 +225,52 @@ Use this skill when the task repeatedly follows a known workflow and you want co
|
|
|
222
225
|
},
|
|
223
226
|
},
|
|
224
227
|
},
|
|
228
|
+
{
|
|
229
|
+
id: "instruction-template",
|
|
230
|
+
type: "instruction",
|
|
231
|
+
title: "Instruction Template",
|
|
232
|
+
description:
|
|
233
|
+
"Reusable markdown instruction scaffold with ref and snippet composition examples.",
|
|
234
|
+
version: "1.0.0",
|
|
235
|
+
tags: ["template", "dx", "instruction"],
|
|
236
|
+
instruction: {
|
|
237
|
+
name: "WORKFLOW.md",
|
|
238
|
+
content: `---
|
|
239
|
+
description: "{{name}} reusable instruction"
|
|
240
|
+
tags: [instruction, workflow]
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
# {{title}}
|
|
244
|
+
|
|
245
|
+
Use this instruction when the task needs repeatable guidance that should be discoverable, targetable by writeback, and composable into agent docs.
|
|
246
|
+
|
|
247
|
+
## Scope
|
|
248
|
+
|
|
249
|
+
- Applies to:
|
|
250
|
+
- Does not apply to:
|
|
251
|
+
- Project-specific overrides:
|
|
252
|
+
|
|
253
|
+
## Guidance
|
|
254
|
+
|
|
255
|
+
- Keep the rule concrete enough that another agent can follow it without chat context.
|
|
256
|
+
- Link deeper reusable guidance with canonical refs such as \`@ai/instructions/VERIFICATION.md\` or \`@project/instructions/TESTING.md\`.
|
|
257
|
+
- Reuse stable partials with snippet markers when the same block appears in more than one rendered doc.
|
|
258
|
+
|
|
259
|
+
## Composition
|
|
260
|
+
|
|
261
|
+
<!-- fclty:global/team/example -->
|
|
262
|
+
<!-- /fclty:global/team/example -->
|
|
263
|
+
|
|
264
|
+
## Writeback Targeting
|
|
265
|
+
|
|
266
|
+
Record durable friction against this instruction with:
|
|
267
|
+
|
|
268
|
+
\`\`\`bash
|
|
269
|
+
fclt ai writeback add --kind missing_context --summary "<what was missing>" --asset instruction:{{assetName}}
|
|
270
|
+
\`\`\`
|
|
271
|
+
`,
|
|
272
|
+
},
|
|
273
|
+
},
|
|
225
274
|
{
|
|
226
275
|
id: "mcp-stdio-template",
|
|
227
276
|
type: "mcp",
|
|
@@ -1461,7 +1510,8 @@ function parseIndexItem(raw: unknown): RemoteIndexItem | null {
|
|
|
1461
1510
|
type !== "skill" &&
|
|
1462
1511
|
type !== "mcp" &&
|
|
1463
1512
|
type !== "agent" &&
|
|
1464
|
-
type !== "snippet"
|
|
1513
|
+
type !== "snippet" &&
|
|
1514
|
+
type !== "instruction"
|
|
1465
1515
|
) {
|
|
1466
1516
|
return null;
|
|
1467
1517
|
}
|
|
@@ -1560,6 +1610,30 @@ function parseIndexItem(raw: unknown): RemoteIndexItem | null {
|
|
|
1560
1610
|
};
|
|
1561
1611
|
}
|
|
1562
1612
|
|
|
1613
|
+
if (type === "instruction") {
|
|
1614
|
+
const instructionRaw = obj.instruction;
|
|
1615
|
+
if (!isPlainObject(instructionRaw)) {
|
|
1616
|
+
return null;
|
|
1617
|
+
}
|
|
1618
|
+
const name =
|
|
1619
|
+
typeof instructionRaw.name === "string" ? instructionRaw.name.trim() : "";
|
|
1620
|
+
const content =
|
|
1621
|
+
typeof instructionRaw.content === "string" ? instructionRaw.content : "";
|
|
1622
|
+
if (!(name && content)) {
|
|
1623
|
+
return null;
|
|
1624
|
+
}
|
|
1625
|
+
return {
|
|
1626
|
+
id,
|
|
1627
|
+
type,
|
|
1628
|
+
title,
|
|
1629
|
+
description,
|
|
1630
|
+
version,
|
|
1631
|
+
sourceUrl,
|
|
1632
|
+
tags,
|
|
1633
|
+
instruction: { name, content },
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1563
1637
|
const snippetRaw = obj.snippet;
|
|
1564
1638
|
if (!isPlainObject(snippetRaw)) {
|
|
1565
1639
|
return null;
|
|
@@ -1731,7 +1805,8 @@ async function loadInstalledState(
|
|
|
1731
1805
|
type !== "skill" &&
|
|
1732
1806
|
type !== "mcp" &&
|
|
1733
1807
|
type !== "agent" &&
|
|
1734
|
-
type !== "snippet"
|
|
1808
|
+
type !== "snippet" &&
|
|
1809
|
+
type !== "instruction"
|
|
1735
1810
|
) {
|
|
1736
1811
|
continue;
|
|
1737
1812
|
}
|
|
@@ -2035,6 +2110,69 @@ async function installSnippetItem(args: {
|
|
|
2035
2110
|
};
|
|
2036
2111
|
}
|
|
2037
2112
|
|
|
2113
|
+
function normalizeInstructionName(value: string): string {
|
|
2114
|
+
const normalized = value
|
|
2115
|
+
.trim()
|
|
2116
|
+
.replaceAll("\\", "/")
|
|
2117
|
+
.replace(LEADING_SLASH_RE, "");
|
|
2118
|
+
if (!normalized) {
|
|
2119
|
+
throw new Error("Instruction name is required");
|
|
2120
|
+
}
|
|
2121
|
+
const withExt = MD_EXT_RE.test(normalized) ? normalized : `${normalized}.md`;
|
|
2122
|
+
if (!isSafeRelativePath(withExt)) {
|
|
2123
|
+
throw new Error(`Invalid instruction name: ${value}`);
|
|
2124
|
+
}
|
|
2125
|
+
return withExt;
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
function instructionTitleFromFileName(fileName: string): string {
|
|
2129
|
+
const base = basename(fileName).replace(MD_EXT_RE, "");
|
|
2130
|
+
return base
|
|
2131
|
+
.split(INSTRUCTION_TITLE_SPLIT_RE)
|
|
2132
|
+
.filter(Boolean)
|
|
2133
|
+
.map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`)
|
|
2134
|
+
.join(" ");
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
function instructionAssetName(fileName: string): string {
|
|
2138
|
+
return basename(fileName).replace(MD_EXT_RE, "");
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
async function installInstructionItem(args: {
|
|
2142
|
+
item: RemoteInstructionItem;
|
|
2143
|
+
installAs?: string;
|
|
2144
|
+
rootDir: string;
|
|
2145
|
+
force: boolean;
|
|
2146
|
+
dryRun: boolean;
|
|
2147
|
+
}): Promise<{ installedAs: string; path: string; changedPaths: string[] }> {
|
|
2148
|
+
const fileName = normalizeInstructionName(
|
|
2149
|
+
args.installAs ?? args.item.instruction.name
|
|
2150
|
+
);
|
|
2151
|
+
const instructionPath = join(args.rootDir, "instructions", fileName);
|
|
2152
|
+
assertInstallPath(instructionPath, join(args.rootDir, "instructions"));
|
|
2153
|
+
if ((await fileExists(instructionPath)) && !args.force) {
|
|
2154
|
+
throw new Error(
|
|
2155
|
+
`Instruction already exists: ${fileName} (use --force to overwrite)`
|
|
2156
|
+
);
|
|
2157
|
+
}
|
|
2158
|
+
if (!args.dryRun) {
|
|
2159
|
+
await mkdir(dirname(instructionPath), { recursive: true });
|
|
2160
|
+
await Bun.write(
|
|
2161
|
+
instructionPath,
|
|
2162
|
+
renderTemplate(args.item.instruction.content, {
|
|
2163
|
+
name: fileName,
|
|
2164
|
+
title: instructionTitleFromFileName(fileName),
|
|
2165
|
+
assetName: instructionAssetName(fileName),
|
|
2166
|
+
})
|
|
2167
|
+
);
|
|
2168
|
+
}
|
|
2169
|
+
return {
|
|
2170
|
+
installedAs: fileName,
|
|
2171
|
+
path: instructionPath,
|
|
2172
|
+
changedPaths: [instructionPath],
|
|
2173
|
+
};
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2038
2176
|
async function installParsedItem(args: {
|
|
2039
2177
|
parsedRef: { index: string; itemId: string };
|
|
2040
2178
|
item: RemoteIndexItem;
|
|
@@ -2076,7 +2214,7 @@ async function installParsedItem(args: {
|
|
|
2076
2214
|
force: args.force,
|
|
2077
2215
|
dryRun: args.dryRun,
|
|
2078
2216
|
});
|
|
2079
|
-
} else {
|
|
2217
|
+
} else if (args.item.type === "snippet") {
|
|
2080
2218
|
writeResult = await installSnippetItem({
|
|
2081
2219
|
item: args.item,
|
|
2082
2220
|
installAs: args.installAs,
|
|
@@ -2084,6 +2222,14 @@ async function installParsedItem(args: {
|
|
|
2084
2222
|
force: args.force,
|
|
2085
2223
|
dryRun: args.dryRun,
|
|
2086
2224
|
});
|
|
2225
|
+
} else {
|
|
2226
|
+
writeResult = await installInstructionItem({
|
|
2227
|
+
item: args.item,
|
|
2228
|
+
installAs: args.installAs,
|
|
2229
|
+
rootDir: args.rootDir,
|
|
2230
|
+
force: args.force,
|
|
2231
|
+
dryRun: args.dryRun,
|
|
2232
|
+
});
|
|
2087
2233
|
}
|
|
2088
2234
|
|
|
2089
2235
|
const result: InstallResult = {
|
|
@@ -2666,6 +2812,9 @@ function printTemplatesHelp() {
|
|
|
2666
2812
|
renderCode(
|
|
2667
2813
|
"fclt templates init agent <name> [--force] [--dry-run]"
|
|
2668
2814
|
),
|
|
2815
|
+
renderCode(
|
|
2816
|
+
"fclt templates init instruction <name> [--force] [--dry-run]"
|
|
2817
|
+
),
|
|
2669
2818
|
renderCode(
|
|
2670
2819
|
"fclt templates init snippet <marker> [--force] [--dry-run]"
|
|
2671
2820
|
),
|
|
@@ -2734,6 +2883,53 @@ function parseLongFlag(argv: string[], flag: string): string | null {
|
|
|
2734
2883
|
return null;
|
|
2735
2884
|
}
|
|
2736
2885
|
|
|
2886
|
+
const TEMPLATE_INIT_VALUE_FLAGS = new Set([
|
|
2887
|
+
"--automation-status",
|
|
2888
|
+
"--cwds",
|
|
2889
|
+
"--name",
|
|
2890
|
+
"--project-root",
|
|
2891
|
+
"--root",
|
|
2892
|
+
"--rrule",
|
|
2893
|
+
"--scope",
|
|
2894
|
+
"--status",
|
|
2895
|
+
]);
|
|
2896
|
+
|
|
2897
|
+
function parseTemplateInitArgs(argv: string[]): {
|
|
2898
|
+
positional: string[];
|
|
2899
|
+
rootArg?: string;
|
|
2900
|
+
} {
|
|
2901
|
+
const positional: string[] = [];
|
|
2902
|
+
let rootArg: string | undefined;
|
|
2903
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
2904
|
+
const arg = argv[i];
|
|
2905
|
+
if (!arg) {
|
|
2906
|
+
continue;
|
|
2907
|
+
}
|
|
2908
|
+
if (arg === "--root") {
|
|
2909
|
+
rootArg = argv[i + 1] ?? undefined;
|
|
2910
|
+
i += 1;
|
|
2911
|
+
continue;
|
|
2912
|
+
}
|
|
2913
|
+
if (arg.startsWith("--root=")) {
|
|
2914
|
+
rootArg = arg.slice("--root=".length);
|
|
2915
|
+
continue;
|
|
2916
|
+
}
|
|
2917
|
+
if (TEMPLATE_INIT_VALUE_FLAGS.has(arg)) {
|
|
2918
|
+
i += 1;
|
|
2919
|
+
continue;
|
|
2920
|
+
}
|
|
2921
|
+
const equalFlag = arg.startsWith("--") ? arg.split("=", 1)[0] : "";
|
|
2922
|
+
if (equalFlag && TEMPLATE_INIT_VALUE_FLAGS.has(equalFlag)) {
|
|
2923
|
+
continue;
|
|
2924
|
+
}
|
|
2925
|
+
if (arg.startsWith("-")) {
|
|
2926
|
+
continue;
|
|
2927
|
+
}
|
|
2928
|
+
positional.push(arg);
|
|
2929
|
+
}
|
|
2930
|
+
return { positional, rootArg };
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2737
2933
|
export async function sourcesCommand(
|
|
2738
2934
|
argv: string[],
|
|
2739
2935
|
ctx: RemoteCommandContext = {}
|
|
@@ -3124,7 +3320,7 @@ export async function templatesCommand(
|
|
|
3124
3320
|
const [kind, ...args] = rest;
|
|
3125
3321
|
if (!kind) {
|
|
3126
3322
|
console.error(
|
|
3127
|
-
"templates init requires a kind (skill|mcp|agent|snippet|agents|claude|operating-model|project-ai|automation)"
|
|
3323
|
+
"templates init requires a kind (skill|mcp|agent|instruction|snippet|agents|claude|operating-model|project-ai|automation)"
|
|
3128
3324
|
);
|
|
3129
3325
|
process.exitCode = 2;
|
|
3130
3326
|
return;
|
|
@@ -3132,7 +3328,8 @@ export async function templatesCommand(
|
|
|
3132
3328
|
const dryRun = args.includes("--dry-run");
|
|
3133
3329
|
const force = args.includes("--force");
|
|
3134
3330
|
const json = args.includes("--json");
|
|
3135
|
-
const
|
|
3331
|
+
const parsedArgs = parseTemplateInitArgs(args);
|
|
3332
|
+
const positional = parsedArgs.positional;
|
|
3136
3333
|
|
|
3137
3334
|
if (kind === "project-ai") {
|
|
3138
3335
|
try {
|
|
@@ -3244,6 +3441,14 @@ export async function templatesCommand(
|
|
|
3244
3441
|
as = normalizedName.endsWith(".toml")
|
|
3245
3442
|
? normalizedName
|
|
3246
3443
|
: `${normalizedName}/agent.toml`;
|
|
3444
|
+
} else if (kind === "instruction") {
|
|
3445
|
+
ref = `${BUILTIN_INDEX_NAME}:instruction-template`;
|
|
3446
|
+
as = positional[0];
|
|
3447
|
+
if (!as) {
|
|
3448
|
+
console.error("templates init instruction requires a <name>");
|
|
3449
|
+
process.exitCode = 2;
|
|
3450
|
+
return;
|
|
3451
|
+
}
|
|
3247
3452
|
} else if (kind === "snippet") {
|
|
3248
3453
|
ref = `${BUILTIN_INDEX_NAME}:snippet-template`;
|
|
3249
3454
|
as = positional[0];
|
|
@@ -3353,7 +3558,7 @@ export async function templatesCommand(
|
|
|
3353
3558
|
dryRun,
|
|
3354
3559
|
force,
|
|
3355
3560
|
homeDir: ctx.homeDir,
|
|
3356
|
-
rootDir: ctx.rootDir,
|
|
3561
|
+
rootDir: parsedArgs.rootArg ?? ctx.rootDir,
|
|
3357
3562
|
cwd: ctx.cwd,
|
|
3358
3563
|
fetchJson: ctx.fetchJson,
|
|
3359
3564
|
fetchText: ctx.fetchText,
|