diff --git a/docs/codex-plugin-capability-matrix.json b/docs/codex-plugin-capability-matrix.json index bbbe094..fa22e1b 100644 --- a/docs/codex-plugin-capability-matrix.json +++ b/docs/codex-plugin-capability-matrix.json @@ -3,7 +3,7 @@ "matrixVersion": 1, "generatedFrom": { "packageVersion": "2.32.0", - "pluginVersion": "0.1.5", + "pluginVersion": "0.1.6", "auditedAt": "2026-07-14T00:00:00Z" }, "dispositions": ["exposed", "withheld", "blocked_safer_api"], diff --git a/plugins/fclt/.codex-plugin/plugin.json b/plugins/fclt/.codex-plugin/plugin.json index 3874fe5..5a20061 100644 --- a/plugins/fclt/.codex-plugin/plugin.json +++ b/plugins/fclt/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "fclt", - "version": "0.1.5", + "version": "0.1.6", "description": "Codex workflows and MCP tools for evidence reconciliation, writeback, evolution, and outcome review.", "author": { "name": "Hack Dance", diff --git a/src/builtin-assets.ts b/src/builtin-assets.ts index 6e7b2eb..08a657b 100644 --- a/src/builtin-assets.ts +++ b/src/builtin-assets.ts @@ -7,7 +7,7 @@ export const BUILTIN_OPERATING_MODEL_FILES = JSON.parse( export const BUILTIN_FCLT_CODEX_PLUGIN_FILES = JSON.parse( // biome-ignore lint/suspicious/noTemplateCurlyInString: Built-in plugin files intentionally contain literal render placeholders. - '{".codex-plugin/plugin.json":"{\\n \\"name\\": \\"fclt\\",\\n \\"version\\": \\"0.1.5\\",\\n \\"description\\": \\"Codex workflows and MCP tools for evidence reconciliation, writeback, evolution, and outcome review.\\",\\n \\"author\\": {\\n \\"name\\": \\"Hack Dance\\",\\n \\"url\\": \\"https://hack.dance\\"\\n },\\n \\"license\\": \\"MIT\\",\\n \\"keywords\\": [\\n \\"fclt\\",\\n \\"facult\\",\\n \\"codex\\",\\n \\"skills\\",\\n \\"mcp\\",\\n \\"writeback\\",\\n \\"evolution\\"\\n ],\\n \\"skills\\": \\"./skills/\\",\\n \\"mcpServers\\": \\"./.mcp.json\\",\\n \\"interface\\": {\\n \\"displayName\\": \\"fclt\\",\\n \\"shortDescription\\": \\"Feedback loops for AI work\\",\\n \\"longDescription\\": \\"Capture signal from real agent work, reconcile configured evidence, turn repeated findings into reviewed capability changes, and verify whether those changes improved the work that produced them.\\",\\n \\"developerName\\": \\"Hack Dance\\",\\n \\"category\\": \\"Productivity\\",\\n \\"capabilities\\": [\\"Read\\", \\"Write\\", \\"MCP\\"],\\n \\"defaultPrompt\\": [\\n \\"Use fclt to check this repo\'s AI capability setup.\\",\\n \\"Reconcile the configured evidence window and explain its coverage.\\",\\n \\"Record useful writeback from this work and review any repeated signal.\\"\\n ],\\n \\"brandColor\\": \\"#166534\\",\\n \\"composerIcon\\": \\"./assets/fclt-mark.png\\",\\n \\"logo\\": \\"./assets/fclt-mark.png\\"\\n }\\n}\\n",".mcp.json":"{\\n \\"mcpServers\\": {\\n \\"fclt\\": {\\n \\"command\\": \\"node\\",\\n \\"args\\": [\\"./scripts/fclt-mcp.cjs\\"],\\n \\"env\\": {\\n \\"FCLT_BIN\\": \\"fclt\\"\\n },\\n \\"cwd\\": \\".\\"\\n }\\n }\\n}\\n","scripts/fclt-mcp.cjs":"#!/usr/bin/env node\\n\\"use strict\\";\\n\\nconst fs = require(\\"node:fs\\");\\nconst os = require(\\"node:os\\");\\nconst path = require(\\"node:path\\");\\nconst runtime = require(\\"./fclt-runtime.cjs\\");\\n\\nconst DEFAULT_TIMEOUT_MS = Number(process.env.FCLT_MCP_TIMEOUT_MS || 60_000);\\nconst ACTIVITY_ACTION_DECIDE_CAPABILITY = \\"activity-action-decide-v1\\";\\nconst ACTIVITY_ACTION_RESOLVE_CAPABILITY = \\"activity-action-resolve-v1\\";\\nconst AUDIT_READ_ONLY_CAPABILITY = \\"audit-read-only-v1\\";\\nconst CONTENT_LENGTH_RE = /Content-Length:\\\\s*(\\\\d+)/i;\\nconst PLUGIN_ROOT = path.resolve(__dirname, \\"..\\");\\n\\nconst tools = [\\n {\\n name: \\"fclt_setup\\",\\n description:\\n \\"Bootstrap or repair the global fclt loop and optionally return an exact no-write project enrollment plan.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n scope: {\\n type: \\"string\\",\\n enum: [\\"global\\", \\"global_and_project\\"],\\n },\\n cwd: { type: \\"string\\" },\\n dryRun: { type: \\"boolean\\" },\\n installCodexPlugin: { type: \\"boolean\\" },\\n approve: { type: \\"boolean\\" },\\n },\\n required: [\\"scope\\"],\\n },\\n },\\n {\\n name: \\"fclt_runtime\\",\\n description:\\n \\"Discover, bootstrap, update, or roll back the verified fclt runtime used by this plugin.\\",\\n inputSchema: {\\n type: \\"object\\",\\n additionalProperties: false,\\n properties: {\\n action: {\\n type: \\"string\\",\\n enum: [\\"status\\", \\"check\\", \\"policy\\", \\"stage\\", \\"apply\\", \\"rollback\\"],\\n },\\n version: { type: \\"string\\" },\\n expectedSha256: { type: \\"string\\", pattern: \\"^[a-f0-9]{64}$\\" },\\n expectedActiveVersion: { type: \\"string\\" },\\n pinnedVersion: { type: \\"string\\" },\\n clearPin: { type: \\"boolean\\" },\\n updateChecksEnabled: { type: \\"boolean\\" },\\n approve: { type: \\"boolean\\" },\\n },\\n },\\n },\\n {\\n name: \\"fclt_capability\\",\\n description:\\n \\"Inspect fclt capability, provenance, templates, snippets, adapters, and managed status without exposing secrets.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n action: {\\n type: \\"string\\",\\n enum: [\\n \\"scan\\",\\n \\"inventory\\",\\n \\"list\\",\\n \\"show\\",\\n \\"find\\",\\n \\"graph\\",\\n \\"adapters\\",\\n \\"managed_status\\",\\n \\"templates_list\\",\\n \\"snippet_list\\",\\n \\"snippet_show\\",\\n ],\\n },\\n scope: { type: \\"string\\", enum: [\\"auto\\", \\"global\\", \\"project\\"] },\\n cwd: { type: \\"string\\" },\\n kind: {\\n type: \\"string\\",\\n enum: [\\n \\"skills\\",\\n \\"mcp\\",\\n \\"agents\\",\\n \\"automations\\",\\n \\"snippets\\",\\n \\"instructions\\",\\n ],\\n },\\n query: { type: \\"string\\" },\\n selector: { type: \\"string\\" },\\n graphMode: { type: \\"string\\", enum: [\\"show\\", \\"deps\\", \\"dependents\\"] },\\n },\\n required: [\\"action\\"],\\n },\\n },\\n {\\n name: \\"fclt_workflow\\",\\n description:\\n \\"Run typed writeback and evolution review operations. Canonical apply and cross-scope promotion are deliberately withheld.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n action: {\\n type: \\"string\\",\\n enum: [\\n \\"writeback_list\\",\\n \\"writeback_show\\",\\n \\"writeback_group\\",\\n \\"writeback_summarize\\",\\n \\"writeback_add\\",\\n \\"writeback_link\\",\\n \\"writeback_disposition\\",\\n \\"evolve_assess\\",\\n \\"evolve_list\\",\\n \\"evolve_show\\",\\n \\"evolve_propose\\",\\n \\"evolve_draft\\",\\n \\"evolve_review\\",\\n \\"evolve_verify\\",\\n ],\\n },\\n scope: { type: \\"string\\", enum: [\\"global\\", \\"project\\"] },\\n cwd: { type: \\"string\\" },\\n id: { type: \\"string\\" },\\n kind: { type: \\"string\\" },\\n category: {\\n type: \\"string\\",\\n enum: [\\"friction\\", \\"opportunity\\", \\"reusable-success\\"],\\n },\\n summary: { type: \\"string\\" },\\n details: { type: \\"string\\", maxLength: 2000 },\\n impact: { type: \\"string\\", maxLength: 1000 },\\n attemptedWorkaround: { type: \\"string\\", maxLength: 1000 },\\n desiredOutcome: { type: \\"string\\", maxLength: 1000 },\\n sensitivity: {\\n type: \\"string\\",\\n enum: [\\"public\\", \\"internal\\", \\"private\\"],\\n },\\n asset: { type: \\"string\\" },\\n evidence: { type: \\"array\\", items: { type: \\"string\\" } },\\n confidence: { type: \\"string\\", enum: [\\"low\\", \\"medium\\", \\"high\\"] },\\n by: { type: \\"string\\", enum: [\\"asset\\", \\"kind\\", \\"domain\\"] },\\n issue: { type: \\"string\\" },\\n disposition: {\\n type: \\"string\\",\\n enum: [\\"propose\\", \\"apply-local\\", \\"task\\", \\"resolve-watch\\", \\"defer\\"],\\n },\\n target: { type: \\"string\\" },\\n nextTrigger: { type: \\"string\\" },\\n expectedOutcome: { type: \\"string\\" },\\n append: { type: \\"string\\" },\\n reason: { type: \\"string\\" },\\n byProposal: { type: \\"string\\" },\\n effectiveness: {\\n type: \\"string\\",\\n enum: [\\"improved\\", \\"unchanged\\", \\"regressed\\", \\"inconclusive\\"],\\n },\\n note: { type: \\"string\\" },\\n approve: { type: \\"boolean\\" },\\n },\\n required: [\\"action\\"],\\n },\\n },\\n {\\n name: \\"fclt_sync\\",\\n description:\\n \\"Inspect managed state or preview a scoped tool sync. Apply and live adoption remain withheld pending transaction-safe APIs.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n action: { type: \\"string\\", enum: [\\"status\\", \\"preview\\"] },\\n scope: { type: \\"string\\", enum: [\\"global\\", \\"project\\"] },\\n cwd: { type: \\"string\\" },\\n tool: { type: \\"string\\" },\\n },\\n required: [\\"action\\", \\"scope\\"],\\n },\\n },\\n {\\n name: \\"fclt_registry\\",\\n description:\\n \\"Search and verify remote capability, run typed source reconciliation reviews, resolve one opaque activity action locator, or record one revision-bound signal decision. Canonical and external mutation remains withheld.\\",\\n inputSchema: {\\n oneOf: [\\n {\\n type: \\"object\\",\\n properties: {\\n action: { const: \\"activity_decide\\" },\\n locator: {\\n type: \\"string\\",\\n pattern: \\"^fclt-act-v[0-9]+\\\\\\\\.[a-f0-9]{64}\\\\\\\\.[a-f0-9]{64}$\\",\\n },\\n decision: {\\n type: \\"string\\",\\n enum: [\\"accept\\", \\"redirect\\", \\"reject\\", \\"defer\\"],\\n },\\n expectedRevision: { type: \\"integer\\", minimum: 1 },\\n actor: {\\n type: \\"string\\",\\n pattern: \\"^[A-Za-z0-9][A-Za-z0-9._:@-]{0,127}$\\",\\n },\\n approvalReference: { type: \\"string\\", minLength: 1, maxLength: 500 },\\n note: { type: \\"string\\", minLength: 1, maxLength: 1000 },\\n redirectTarget: { type: \\"string\\", minLength: 1, maxLength: 500 },\\n approve: { const: true },\\n },\\n required: [\\n \\"action\\",\\n \\"locator\\",\\n \\"decision\\",\\n \\"expectedRevision\\",\\n \\"actor\\",\\n \\"approve\\",\\n ],\\n allOf: [\\n {\\n oneOf: [\\n {\\n required: [\\"approvalReference\\"],\\n not: { required: [\\"note\\"] },\\n },\\n {\\n required: [\\"note\\"],\\n not: { required: [\\"approvalReference\\"] },\\n },\\n ],\\n },\\n ],\\n additionalProperties: false,\\n },\\n {\\n type: \\"object\\",\\n properties: {\\n action: { const: \\"activity_resolve\\" },\\n locator: {\\n type: \\"string\\",\\n pattern: \\"^fclt-act-v[0-9]+\\\\\\\\.[a-f0-9]{64}\\\\\\\\.[a-f0-9]{64}$\\",\\n },\\n },\\n required: [\\"action\\", \\"locator\\"],\\n additionalProperties: false,\\n },\\n {\\n type: \\"object\\",\\n properties: {\\n action: {\\n type: \\"string\\",\\n enum: [\\n \\"search\\",\\n \\"verify_source\\",\\n \\"source_list\\",\\n \\"install_preview\\",\\n \\"update_check\\",\\n \\"reconcile_status\\",\\n \\"reconcile\\",\\n ],\\n },\\n scope: { type: \\"string\\", enum: [\\"global\\", \\"project\\"] },\\n cwd: { type: \\"string\\" },\\n query: { type: \\"string\\" },\\n source: { type: \\"string\\" },\\n item: { type: \\"string\\" },\\n as: { type: \\"string\\" },\\n since: {\\n type: \\"string\\",\\n pattern:\\n \\"^\\\\\\\\d{4}-\\\\\\\\d{2}-\\\\\\\\d{2}(?:T\\\\\\\\d{2}:\\\\\\\\d{2}:\\\\\\\\d{2}(?:\\\\\\\\.\\\\\\\\d+)?(?:Z|[+-]\\\\\\\\d{2}:\\\\\\\\d{2}))?$\\",\\n },\\n until: {\\n type: \\"string\\",\\n pattern:\\n \\"^\\\\\\\\d{4}-\\\\\\\\d{2}-\\\\\\\\d{2}(?:T\\\\\\\\d{2}:\\\\\\\\d{2}:\\\\\\\\d{2}(?:\\\\\\\\.\\\\\\\\d+)?(?:Z|[+-]\\\\\\\\d{2}:\\\\\\\\d{2}))?$\\",\\n },\\n sourceIds: {\\n type: \\"array\\",\\n items: {\\n type: \\"string\\",\\n pattern: \\"^[A-Za-z0-9][A-Za-z0-9._-]*$\\",\\n },\\n },\\n incremental: { type: \\"boolean\\" },\\n },\\n required: [\\"action\\"],\\n additionalProperties: false,\\n },\\n ],\\n },\\n },\\n {\\n name: \\"fclt_audit\\",\\n description:\\n \\"Run a structured, redacted, non-interactive fclt security audit with no report or index writes.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n action: { type: \\"string\\", enum: [\\"scan\\"] },\\n cwd: { type: \\"string\\" },\\n target: { type: \\"string\\" },\\n severity: {\\n type: \\"string\\",\\n enum: [\\"critical\\", \\"high\\", \\"medium\\", \\"low\\"],\\n },\\n },\\n required: [\\"action\\"],\\n },\\n },\\n {\\n name: \\"fclt_automation\\",\\n description:\\n \\"Read one aggregate activity set across all configured loops by default, or inspect one explicit loop scope. Schedule and canonical mutation remain CLI-only.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n action: {\\n type: \\"string\\",\\n enum: [\\n \\"autosync_status\\",\\n \\"loop_status\\",\\n \\"loop_activity\\",\\n \\"loop_preview\\",\\n ],\\n },\\n scope: {\\n type: \\"string\\",\\n enum: [\\"all\\", \\"global\\", \\"project\\"],\\n },\\n cwd: { type: \\"string\\" },\\n tool: { type: \\"string\\" },\\n },\\n required: [\\"action\\"],\\n oneOf: [\\n {\\n properties: {\\n action: { const: \\"loop_activity\\" },\\n scope: {\\n type: \\"string\\",\\n enum: [\\"all\\", \\"global\\", \\"project\\"],\\n default: \\"all\\",\\n },\\n },\\n required: [\\"action\\"],\\n },\\n {\\n properties: {\\n action: {\\n enum: [\\"autosync_status\\", \\"loop_status\\", \\"loop_preview\\"],\\n },\\n scope: { type: \\"string\\", enum: [\\"global\\", \\"project\\"] },\\n },\\n required: [\\"action\\", \\"scope\\"],\\n },\\n ],\\n },\\n },\\n {\\n name: \\"fclt_status\\",\\n description:\\n \\"Return fclt status for the current, global, or project scope.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n scope: { type: \\"string\\", enum: [\\"auto\\", \\"global\\", \\"project\\"] },\\n cwd: { type: \\"string\\" },\\n },\\n },\\n },\\n {\\n name: \\"fclt_doctor\\",\\n description: \\"Run read-only fclt doctor checks and return JSON output.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n scope: { type: \\"string\\", enum: [\\"auto\\", \\"global\\", \\"project\\"] },\\n cwd: { type: \\"string\\" },\\n },\\n },\\n },\\n {\\n name: \\"fclt_paths\\",\\n description: \\"Return canonical, generated, review, and runtime fclt paths.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n scope: { type: \\"string\\", enum: [\\"auto\\", \\"global\\", \\"project\\"] },\\n cwd: { type: \\"string\\" },\\n },\\n },\\n },\\n {\\n name: \\"fclt_init_operating_model\\",\\n description: \\"Install or update the built-in operating-model pack.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n scope: { type: \\"string\\", enum: [\\"global\\", \\"project\\"] },\\n cwd: { type: \\"string\\" },\\n update: { type: \\"boolean\\" },\\n dryRun: { type: \\"boolean\\" },\\n force: { type: \\"boolean\\" },\\n approve: { type: \\"boolean\\" },\\n },\\n required: [\\"scope\\"],\\n },\\n },\\n {\\n name: \\"fclt_writeback_add\\",\\n description: \\"Record a durable fclt writeback with evidence.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n scope: { type: \\"string\\", enum: [\\"global\\", \\"project\\"] },\\n cwd: { type: \\"string\\" },\\n kind: { type: \\"string\\" },\\n category: {\\n type: \\"string\\",\\n enum: [\\"friction\\", \\"opportunity\\", \\"reusable-success\\"],\\n },\\n summary: { type: \\"string\\" },\\n details: { type: \\"string\\", maxLength: 2000 },\\n impact: { type: \\"string\\", maxLength: 1000 },\\n attemptedWorkaround: { type: \\"string\\", maxLength: 1000 },\\n desiredOutcome: { type: \\"string\\", maxLength: 1000 },\\n sensitivity: {\\n type: \\"string\\",\\n enum: [\\"public\\", \\"internal\\", \\"private\\"],\\n },\\n asset: { type: \\"string\\" },\\n evidence: { type: \\"string\\" },\\n confidence: {\\n type: \\"string\\",\\n enum: [\\"low\\", \\"medium\\", \\"high\\"],\\n },\\n approve: { type: \\"boolean\\" },\\n },\\n required: [\\"scope\\", \\"kind\\", \\"summary\\", \\"evidence\\", \\"approve\\"],\\n },\\n },\\n {\\n name: \\"fclt_writeback_review\\",\\n description: \\"List, group, or summarize current fclt writebacks.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n scope: { type: \\"string\\", enum: [\\"global\\", \\"project\\"] },\\n cwd: { type: \\"string\\" },\\n mode: { type: \\"string\\", enum: [\\"list\\", \\"group\\", \\"summarize\\"] },\\n by: { type: \\"string\\" },\\n },\\n },\\n },\\n {\\n name: \\"fclt_evolve\\",\\n description:\\n \\"Assess, list, propose, draft, or review fclt evolution proposals.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n scope: { type: \\"string\\", enum: [\\"auto\\", \\"global\\", \\"project\\"] },\\n cwd: { type: \\"string\\" },\\n action: {\\n type: \\"string\\",\\n enum: [\\"assess\\", \\"list\\", \\"propose\\", \\"draft\\", \\"review\\", \\"show\\"],\\n },\\n id: { type: \\"string\\" },\\n asset: { type: \\"string\\" },\\n approve: { type: \\"boolean\\" },\\n },\\n },\\n },\\n];\\n\\nfor (const tool of tools) {\\n if (!tool.inputSchema.oneOf || tool.inputSchema.properties) {\\n tool.inputSchema.additionalProperties = false;\\n }\\n}\\n\\nfunction isPlainObject(value) {\\n return Boolean(value) && typeof value === \\"object\\" && !Array.isArray(value);\\n}\\n\\nfunction validateToolArguments(name, args) {\\n if (!isPlainObject(args)) {\\n throw new Error(`${name} arguments must be an object`);\\n }\\n const tool = tools.find((entry) => entry.name === name);\\n if (!tool) {\\n throw new Error(`Unknown tool: ${name}`);\\n }\\n const rootSchema = tool.inputSchema;\\n const schema =\\n rootSchema.oneOf && !rootSchema.properties\\n ? rootSchema.oneOf.find((branch) => {\\n const action = branch.properties?.action;\\n return (\\n action?.const === args.action || action?.enum?.includes(args.action)\\n );\\n })\\n : rootSchema;\\n if (!schema) {\\n throw new Error(`${name}.action is not an allowed value`);\\n }\\n const properties = schema.properties || {};\\n const unknown = Object.keys(args).filter((key) => !(key in properties));\\n if (unknown.length > 0) {\\n throw new Error(\\n `${name} received unknown argument fields: ${unknown.join(\\", \\")}`\\n );\\n }\\n for (const required of schema.required || []) {\\n if (!(required in args)) {\\n throw new Error(`${name} requires ${required}`);\\n }\\n }\\n for (const constraint of schema.allOf || []) {\\n if (Array.isArray(constraint.oneOf)) {\\n const matches = constraint.oneOf.filter((branch) => {\\n const hasRequired = (branch.required || []).every((key) => key in args);\\n const violatesNot = branch.not?.required\\n ? branch.not.required.every((key) => key in args)\\n : false;\\n return hasRequired && !violatesNot;\\n });\\n if (matches.length !== 1) {\\n throw new Error(\\n `${name} arguments do not match exactly one allowed shape`\\n );\\n }\\n }\\n }\\n for (const [key, value] of Object.entries(args)) {\\n const property = properties[key];\\n if (property.const !== undefined && value !== property.const) {\\n throw new Error(`${name}.${key} is not an allowed value`);\\n }\\n if (!property.type) {\\n continue;\\n }\\n const validType =\\n property.type === \\"array\\"\\n ? Array.isArray(value)\\n : property.type === \\"object\\"\\n ? isPlainObject(value)\\n : property.type === \\"integer\\"\\n ? Number.isSafeInteger(value)\\n : typeof value === property.type;\\n if (!validType) {\\n throw new Error(`${name}.${key} must be ${property.type}`);\\n }\\n if (property.enum && !property.enum.includes(value)) {\\n throw new Error(`${name}.${key} is not an allowed value`);\\n }\\n if (\\n typeof value === \\"string\\" &&\\n ((property.minLength !== undefined &&\\n value.length < property.minLength) ||\\n (property.maxLength !== undefined && value.length > property.maxLength))\\n ) {\\n throw new Error(`${name}.${key} has an invalid length`);\\n }\\n if (\\n typeof value === \\"number\\" &&\\n property.minimum !== undefined &&\\n value < property.minimum\\n ) {\\n throw new Error(`${name}.${key} is below the minimum`);\\n }\\n if (property.pattern && !new RegExp(property.pattern).test(value)) {\\n throw new Error(`${name}.${key} has an invalid format`);\\n }\\n if (\\n property.type === \\"array\\" &&\\n property.items?.type &&\\n value.some((item) => typeof item !== property.items.type)\\n ) {\\n throw new Error(`${name}.${key} contains an invalid item`);\\n }\\n if (\\n property.type === \\"array\\" &&\\n property.items?.pattern &&\\n value.some((item) => !new RegExp(property.items.pattern).test(item))\\n ) {\\n throw new Error(`${name}.${key} contains an invalid item`);\\n }\\n }\\n}\\n\\nfunction scopeArgs(scope) {\\n if (scope === \\"global\\") {\\n return [\\"--global\\"];\\n }\\n if (scope === \\"project\\") {\\n return [\\"--project\\"];\\n }\\n return [];\\n}\\n\\nfunction boolFlag(name, value) {\\n return value ? [name] : [];\\n}\\n\\nfunction stringFlag(name, value) {\\n return typeof value === \\"string\\" && value.trim() ? [name, value] : [];\\n}\\n\\nfunction repeatedStringFlag(name, values) {\\n return Array.isArray(values)\\n ? values.flatMap((value) => stringFlag(name, value))\\n : [];\\n}\\n\\nfunction requireString(name, value) {\\n if (typeof value !== \\"string\\" || !value.trim()) {\\n throw new Error(`${name} is required`);\\n }\\n return value;\\n}\\n\\nfunction requireMutationApproval(name, args) {\\n if (args.scope !== \\"global\\" && args.scope !== \\"project\\") {\\n throw new Error(`${name} requires an explicit global or project scope`);\\n }\\n if (args.approve !== true) {\\n throw new Error(`${name} requires approve=true`);\\n }\\n}\\n\\nfunction capabilityCommand(args) {\\n const action = args.action;\\n if (action === \\"scan\\") {\\n return [\\"scan\\", \\"--json\\"];\\n }\\n if (action === \\"inventory\\") {\\n return [\\"inventory\\", ...scopeArgs(args.scope), \\"--json\\"];\\n }\\n if (action === \\"list\\") {\\n return [\\"list\\", args.kind || \\"skills\\", ...scopeArgs(args.scope), \\"--json\\"];\\n }\\n if (action === \\"show\\") {\\n return [\\n \\"show\\",\\n requireString(\\"selector\\", args.selector),\\n ...scopeArgs(args.scope),\\n ];\\n }\\n if (action === \\"find\\") {\\n return [\\n \\"find\\",\\n requireString(\\"query\\", args.query),\\n ...scopeArgs(args.scope),\\n \\"--json\\",\\n ];\\n }\\n if (action === \\"graph\\") {\\n return [\\n \\"graph\\",\\n args.graphMode || \\"show\\",\\n requireString(\\"selector\\", args.selector),\\n ...scopeArgs(args.scope),\\n \\"--json\\",\\n ];\\n }\\n if (action === \\"adapters\\") {\\n return [\\"adapters\\", \\"--json\\"];\\n }\\n if (action === \\"managed_status\\") {\\n return [\\"managed\\", ...scopeArgs(args.scope)];\\n }\\n if (action === \\"templates_list\\") {\\n return [\\"templates\\", \\"list\\", \\"--json\\"];\\n }\\n if (action === \\"snippet_list\\") {\\n return [\\"snippets\\", \\"list\\", \\"--json\\"];\\n }\\n if (action === \\"snippet_show\\") {\\n return [\\n \\"snippets\\",\\n \\"show\\",\\n requireString(\\"selector\\", args.selector),\\n \\"--json\\",\\n ];\\n }\\n throw new Error(`Unsupported capability action: ${action}`);\\n}\\n\\nconst WORKFLOW_MUTATIONS = new Set([\\n \\"writeback_add\\",\\n \\"writeback_link\\",\\n \\"writeback_disposition\\",\\n \\"writeback_dismiss\\",\\n \\"writeback_promote\\",\\n \\"evolve_propose\\",\\n \\"evolve_draft\\",\\n \\"evolve_review\\",\\n \\"evolve_accept\\",\\n \\"evolve_reject\\",\\n \\"evolve_supersede\\",\\n \\"evolve_verify\\",\\n]);\\n\\nconst WORKFLOW_ACTION_FIELDS = {\\n writeback_list: [],\\n writeback_show: [\\"id\\"],\\n writeback_group: [\\"by\\"],\\n writeback_summarize: [\\"by\\"],\\n writeback_add: [\\n \\"kind\\",\\n \\"category\\",\\n \\"summary\\",\\n \\"details\\",\\n \\"impact\\",\\n \\"attemptedWorkaround\\",\\n \\"desiredOutcome\\",\\n \\"sensitivity\\",\\n \\"asset\\",\\n \\"evidence\\",\\n \\"confidence\\",\\n \\"approve\\",\\n ],\\n writeback_link: [\\"id\\", \\"issue\\", \\"approve\\"],\\n writeback_disposition: [\\n \\"id\\",\\n \\"disposition\\",\\n \\"target\\",\\n \\"nextTrigger\\",\\n \\"expectedOutcome\\",\\n \\"approve\\",\\n ],\\n evolve_assess: [\\"asset\\"],\\n evolve_list: [],\\n evolve_show: [\\"id\\"],\\n evolve_propose: [\\"asset\\", \\"approve\\"],\\n evolve_draft: [\\"id\\", \\"append\\", \\"approve\\"],\\n evolve_review: [\\"id\\", \\"approve\\"],\\n evolve_verify: [\\"id\\", \\"effectiveness\\", \\"evidence\\", \\"note\\", \\"approve\\"],\\n};\\n\\nfunction requireOnlyWorkflowFields(args) {\\n const fields = WORKFLOW_ACTION_FIELDS[args.action];\\n if (!fields) {\\n return;\\n }\\n const allowed = new Set([\\"action\\", \\"scope\\", \\"cwd\\", ...fields]);\\n const unexpected = Object.keys(args).filter((key) => !allowed.has(key));\\n if (unexpected.length > 0) {\\n throw new Error(\\n `${args.action} received unsupported fields: ${unexpected.join(\\", \\")}`\\n );\\n }\\n}\\n\\nfunction workflowCommand(args) {\\n const action = args.action;\\n requireOnlyWorkflowFields(args);\\n if (WORKFLOW_MUTATIONS.has(action)) {\\n requireMutationApproval(action, args);\\n }\\n const scope = scopeArgs(args.scope);\\n if (action === \\"writeback_list\\") {\\n return [\\"ai\\", \\"writeback\\", ...scope, \\"list\\", \\"--json\\"];\\n }\\n if (action === \\"writeback_show\\") {\\n return [\\n \\"ai\\",\\n \\"writeback\\",\\n ...scope,\\n \\"show\\",\\n requireString(\\"id\\", args.id),\\n \\"--json\\",\\n ];\\n }\\n if (action === \\"writeback_group\\" || action === \\"writeback_summarize\\") {\\n return [\\n \\"ai\\",\\n \\"writeback\\",\\n ...scope,\\n action === \\"writeback_group\\" ? \\"group\\" : \\"summarize\\",\\n ...stringFlag(\\"--by\\", args.by),\\n \\"--json\\",\\n ];\\n }\\n if (action === \\"writeback_add\\") {\\n if (!Array.isArray(args.evidence) || args.evidence.length === 0) {\\n throw new Error(\\"writeback_add requires at least one evidence reference\\");\\n }\\n return [\\n \\"ai\\",\\n \\"writeback\\",\\n ...scope,\\n \\"add\\",\\n \\"--kind\\",\\n requireString(\\"kind\\", args.kind),\\n \\"--summary\\",\\n requireString(\\"summary\\", args.summary),\\n ...stringFlag(\\"--asset\\", args.asset),\\n ...stringFlag(\\"--category\\", args.category),\\n ...stringFlag(\\"--details\\", args.details),\\n ...stringFlag(\\"--impact\\", args.impact),\\n ...stringFlag(\\"--attempted-workaround\\", args.attemptedWorkaround),\\n ...stringFlag(\\"--desired-outcome\\", args.desiredOutcome),\\n ...stringFlag(\\"--sensitivity\\", args.sensitivity),\\n ...repeatedStringFlag(\\"--evidence\\", args.evidence),\\n ...stringFlag(\\"--confidence\\", args.confidence),\\n \\"--json\\",\\n ];\\n }\\n if (action === \\"writeback_link\\") {\\n return [\\n \\"ai\\",\\n \\"writeback\\",\\n ...scope,\\n \\"link\\",\\n requireString(\\"id\\", args.id),\\n \\"--issue\\",\\n requireString(\\"issue\\", args.issue),\\n ];\\n }\\n if (action === \\"writeback_disposition\\") {\\n return [\\n \\"ai\\",\\n \\"writeback\\",\\n ...scope,\\n \\"disposition\\",\\n requireString(\\"id\\", args.id),\\n \\"--type\\",\\n requireString(\\"disposition\\", args.disposition),\\n ...stringFlag(\\"--target\\", args.target),\\n ...stringFlag(\\"--next-trigger\\", args.nextTrigger),\\n ...stringFlag(\\"--expected-outcome\\", args.expectedOutcome),\\n ];\\n }\\n if (action === \\"writeback_dismiss\\" || action === \\"writeback_promote\\") {\\n return [\\n \\"ai\\",\\n \\"writeback\\",\\n ...scope,\\n action === \\"writeback_dismiss\\" ? \\"dismiss\\" : \\"promote\\",\\n requireString(\\"id\\", args.id),\\n ];\\n }\\n if (action === \\"evolve_assess\\") {\\n return [\\n \\"ai\\",\\n \\"evolve\\",\\n ...scope,\\n \\"assess\\",\\n ...stringFlag(\\"--asset\\", args.asset),\\n \\"--json\\",\\n ];\\n }\\n if (action === \\"evolve_list\\") {\\n return [\\"ai\\", \\"evolve\\", ...scope, \\"list\\", \\"--json\\"];\\n }\\n if (action === \\"evolve_show\\") {\\n return [\\n \\"ai\\",\\n \\"evolve\\",\\n ...scope,\\n \\"show\\",\\n requireString(\\"id\\", args.id),\\n \\"--json\\",\\n ];\\n }\\n if (action === \\"evolve_propose\\") {\\n return [\\n \\"ai\\",\\n \\"evolve\\",\\n ...scope,\\n \\"propose\\",\\n \\"--asset\\",\\n requireString(\\"asset\\", args.asset),\\n \\"--json\\",\\n ];\\n }\\n if (action === \\"evolve_draft\\") {\\n return [\\n \\"ai\\",\\n \\"evolve\\",\\n ...scope,\\n \\"draft\\",\\n requireString(\\"id\\", args.id),\\n ...stringFlag(\\"--append\\", args.append),\\n ];\\n }\\n if (action === \\"evolve_review\\" || action === \\"evolve_accept\\") {\\n return [\\n \\"ai\\",\\n \\"evolve\\",\\n ...scope,\\n action === \\"evolve_review\\" ? \\"review\\" : \\"accept\\",\\n requireString(\\"id\\", args.id),\\n ];\\n }\\n if (action === \\"evolve_reject\\") {\\n return [\\n \\"ai\\",\\n \\"evolve\\",\\n ...scope,\\n \\"reject\\",\\n requireString(\\"id\\", args.id),\\n \\"--reason\\",\\n requireString(\\"reason\\", args.reason),\\n ];\\n }\\n if (action === \\"evolve_supersede\\") {\\n return [\\n \\"ai\\",\\n \\"evolve\\",\\n ...scope,\\n \\"supersede\\",\\n requireString(\\"id\\", args.id),\\n \\"--by\\",\\n requireString(\\"byProposal\\", args.byProposal),\\n ];\\n }\\n if (action === \\"evolve_verify\\") {\\n if (!Array.isArray(args.evidence) || args.evidence.length === 0) {\\n throw new Error(\\"evolve_verify requires at least one evidence reference\\");\\n }\\n return [\\n \\"ai\\",\\n \\"evolve\\",\\n ...scope,\\n \\"verify\\",\\n requireString(\\"id\\", args.id),\\n \\"--effectiveness\\",\\n requireString(\\"effectiveness\\", args.effectiveness),\\n ...repeatedStringFlag(\\"--evidence\\", args.evidence),\\n ...stringFlag(\\"--note\\", args.note),\\n ];\\n }\\n throw new Error(`Unsupported workflow action: ${action}`);\\n}\\n\\nfunction syncCommand(args) {\\n if (args.action === \\"status\\") {\\n return [\\"managed\\", ...scopeArgs(args.scope)];\\n }\\n if (args.action === \\"preview\\") {\\n return [\\n \\"sync\\",\\n ...(args.tool ? [args.tool] : []),\\n \\"--dry-run\\",\\n ...scopeArgs(args.scope),\\n ];\\n }\\n throw new Error(`Unsupported sync action: ${args.action}`);\\n}\\n\\nfunction requireOnlyRegistryFields(args, fields) {\\n const allowed = new Set([\\"action\\", \\"scope\\", \\"cwd\\", ...fields]);\\n const unexpected = Object.keys(args).filter((key) => !allowed.has(key));\\n if (unexpected.length > 0) {\\n throw new Error(\\n `${args.action} received unsupported fields: ${unexpected.join(\\", \\")}`\\n );\\n }\\n}\\n\\nfunction registryCommand(args) {\\n if (args.action === \\"activity_decide\\") {\\n const expectedFields = new Set([\\n \\"action\\",\\n \\"locator\\",\\n \\"decision\\",\\n \\"expectedRevision\\",\\n \\"actor\\",\\n \\"approvalReference\\",\\n \\"note\\",\\n \\"redirectTarget\\",\\n \\"approve\\",\\n ]);\\n const unexpected = Object.keys(args).filter(\\n (key) => !expectedFields.has(key)\\n );\\n if (unexpected.length > 0) {\\n throw new Error(\\n `activity_decide received unsupported fields: ${unexpected.join(\\", \\")}`\\n );\\n }\\n if (args.approve !== true) {\\n throw new Error(\\"activity_decide requires approve=true\\");\\n }\\n if (!Number.isSafeInteger(args.expectedRevision)) {\\n throw new Error(\\n \\"activity_decide requires expectedRevision as an integer\\"\\n );\\n }\\n return [\\n \\"ai\\",\\n \\"loop\\",\\n \\"decide\\",\\n requireString(\\"locator\\", args.locator),\\n \\"--decision\\",\\n requireString(\\"decision\\", args.decision),\\n \\"--expected-revision\\",\\n String(args.expectedRevision),\\n \\"--actor\\",\\n requireString(\\"actor\\", args.actor),\\n ...stringFlag(\\"--approval-ref\\", args.approvalReference),\\n ...stringFlag(\\"--note\\", args.note),\\n ...stringFlag(\\"--redirect-target\\", args.redirectTarget),\\n \\"--approve\\",\\n \\"--json\\",\\n ];\\n }\\n if (args.action === \\"activity_resolve\\") {\\n const unexpected = Object.keys(args).filter(\\n (key) => key !== \\"action\\" && key !== \\"locator\\"\\n );\\n if (unexpected.length > 0) {\\n throw new Error(\\n `activity_resolve received unsupported fields: ${unexpected.join(\\", \\")}`\\n );\\n }\\n return [\\n \\"ai\\",\\n \\"loop\\",\\n \\"resolve\\",\\n requireString(\\"locator\\", args.locator),\\n \\"--json\\",\\n ];\\n }\\n if (\\n args.scope === \\"project\\" &&\\n args.action !== \\"reconcile_status\\" &&\\n args.action !== \\"reconcile\\"\\n ) {\\n throw new Error(`${args.action} only supports global scope`);\\n }\\n if (args.action === \\"search\\") {\\n requireOnlyRegistryFields(args, [\\"query\\"]);\\n return [\\"search\\", requireString(\\"query\\", args.query), \\"--json\\"];\\n }\\n if (args.action === \\"verify_source\\") {\\n requireOnlyRegistryFields(args, [\\"source\\"]);\\n return [\\"verify-source\\", requireString(\\"source\\", args.source), \\"--json\\"];\\n }\\n if (args.action === \\"source_list\\") {\\n requireOnlyRegistryFields(args, []);\\n return [\\"sources\\", \\"list\\", \\"--json\\"];\\n }\\n if (args.action === \\"install_preview\\") {\\n requireOnlyRegistryFields(args, [\\"item\\", \\"as\\"]);\\n return [\\n \\"install\\",\\n requireString(\\"item\\", args.item),\\n ...stringFlag(\\"--as\\", args.as),\\n \\"--dry-run\\",\\n \\"--strict-source-trust\\",\\n \\"--json\\",\\n ];\\n }\\n if (args.action === \\"update_check\\") {\\n requireOnlyRegistryFields(args, []);\\n return [\\"update\\", \\"--strict-source-trust\\", \\"--json\\"];\\n }\\n if (args.action === \\"reconcile_status\\") {\\n requireOnlyRegistryFields(args, []);\\n if (args.scope !== \\"global\\" && args.scope !== \\"project\\") {\\n throw new Error(\\n \\"reconcile_status requires an explicit global or project scope\\"\\n );\\n }\\n return [\\"ai\\", \\"review\\", ...scopeArgs(args.scope), \\"status\\", \\"--json\\"];\\n }\\n if (args.action === \\"reconcile\\") {\\n requireOnlyRegistryFields(args, [\\n \\"since\\",\\n \\"until\\",\\n \\"sourceIds\\",\\n \\"incremental\\",\\n ]);\\n if (args.scope !== \\"global\\" && args.scope !== \\"project\\") {\\n throw new Error(\\"reconcile requires an explicit global or project scope\\");\\n }\\n return [\\n \\"ai\\",\\n \\"review\\",\\n ...scopeArgs(args.scope),\\n \\"reconcile\\",\\n \\"--since\\",\\n requireString(\\"since\\", args.since),\\n ...stringFlag(\\"--until\\", args.until),\\n ...repeatedStringFlag(\\"--source\\", args.sourceIds),\\n ...boolFlag(\\"--incremental\\", args.incremental),\\n \\"--json\\",\\n ];\\n }\\n throw new Error(`Unsupported registry action: ${args.action}`);\\n}\\n\\nfunction isSubpath(child, parent) {\\n const relative = path.relative(parent, child);\\n return (\\n relative === \\"\\" || !(relative.startsWith(\\"..\\") || path.isAbsolute(relative))\\n );\\n}\\n\\nfunction isDirectory(candidate) {\\n try {\\n return fs.statSync(candidate).isDirectory();\\n } catch {\\n return false;\\n }\\n}\\n\\nfunction resolveWorkspaceCwd({ allowHomeFallback = true } = {}) {\\n const candidates = [\\n process.env.FCLT_MCP_WORKSPACE_CWD,\\n process.env.INIT_CWD,\\n process.env.PWD,\\n ];\\n for (const candidate of candidates) {\\n if (typeof candidate !== \\"string\\" || !candidate.trim()) {\\n continue;\\n }\\n const resolved = path.resolve(candidate);\\n const isHome = resolved === path.resolve(os.homedir());\\n if (\\n (allowHomeFallback || !isHome) &&\\n !isSubpath(resolved, PLUGIN_ROOT) &&\\n isDirectory(resolved)\\n ) {\\n return resolved;\\n }\\n }\\n if (allowHomeFallback && isDirectory(os.homedir())) {\\n return os.homedir();\\n }\\n return undefined;\\n}\\n\\nfunction resolveToolCwd(name, args = {}) {\\n if (typeof args.cwd === \\"string\\" && args.cwd.trim()) {\\n return args.cwd;\\n }\\n const inferred = resolveWorkspaceCwd({\\n allowHomeFallback: args.scope !== \\"project\\",\\n });\\n if (inferred) {\\n return inferred;\\n }\\n if (args.scope === \\"project\\") {\\n throw new Error(\\n `${name} with project scope requires a cwd for the target workspace`\\n );\\n }\\n return process.cwd();\\n}\\n\\nfunction commandForTool(name, args = {}) {\\n switch (name) {\\n case \\"fclt_setup\\": {\\n const apply = args.dryRun === false;\\n if (apply && args.approve !== true) {\\n throw new Error(\\"fclt_setup apply requires approve=true\\");\\n }\\n if (\\n args.scope === \\"global_and_project\\" &&\\n (typeof args.cwd !== \\"string\\" || !args.cwd.trim())\\n ) {\\n throw new Error(\\n \\"fclt_setup global_and_project scope requires an explicit cwd\\"\\n );\\n }\\n return [\\n \\"setup\\",\\n \\"--json\\",\\n ...(args.scope === \\"global_and_project\\" ? [\\"--include-project\\"] : []),\\n ...(apply ? [] : [\\"--dry-run\\"]),\\n ...(args.installCodexPlugin === false ? [\\"--no-codex-plugin\\"] : []),\\n ];\\n }\\n case \\"fclt_capability\\":\\n return capabilityCommand(args);\\n case \\"fclt_workflow\\":\\n return workflowCommand(args);\\n case \\"fclt_sync\\":\\n return syncCommand(args);\\n case \\"fclt_registry\\":\\n return registryCommand(args);\\n case \\"fclt_audit\\":\\n return [\\n \\"audit\\",\\n \\"--non-interactive\\",\\n ...(args.target ? [args.target] : []),\\n ...stringFlag(\\"--severity\\", args.severity),\\n \\"--json\\",\\n ];\\n case \\"fclt_automation\\":\\n if (\\n args.action !== \\"loop_activity\\" &&\\n args.scope !== \\"global\\" &&\\n args.scope !== \\"project\\"\\n ) {\\n throw new Error(`${args.action} requires global or project scope`);\\n }\\n if (args.action === \\"autosync_status\\") {\\n return [\\n \\"autosync\\",\\n \\"status\\",\\n ...(args.tool ? [args.tool] : []),\\n ...scopeArgs(args.scope),\\n ];\\n }\\n if (args.tool) {\\n throw new Error(`${args.action} does not accept tool`);\\n }\\n if (args.action === \\"loop_status\\") {\\n return [\\"ai\\", \\"loop\\", ...scopeArgs(args.scope), \\"status\\", \\"--json\\"];\\n }\\n if (args.action === \\"loop_activity\\") {\\n return [\\n \\"ai\\",\\n \\"loop\\",\\n ...scopeArgs(args.scope),\\n \\"activity\\",\\n ...(args.scope === \\"global\\" || args.scope === \\"project\\"\\n ? []\\n : [\\"--all\\"]),\\n \\"--json\\",\\n ];\\n }\\n if (args.action === \\"loop_preview\\") {\\n return [\\n \\"ai\\",\\n \\"loop\\",\\n ...scopeArgs(args.scope),\\n \\"run\\",\\n \\"--dry-run\\",\\n \\"--json\\",\\n ];\\n }\\n throw new Error(`Unsupported automation action: ${args.action}`);\\n case \\"fclt_status\\":\\n return [\\"status\\", ...scopeArgs(args.scope), \\"--json\\"];\\n case \\"fclt_doctor\\":\\n return [\\"doctor\\", ...scopeArgs(args.scope), \\"--json\\"];\\n case \\"fclt_paths\\":\\n return [\\"paths\\", ...scopeArgs(args.scope), \\"--json\\"];\\n case \\"fclt_init_operating_model\\":\\n if (args.dryRun === false && args.approve !== true) {\\n throw new Error(\\n \\"fclt_init_operating_model apply requires approve=true\\"\\n );\\n }\\n if (args.force === true && args.approve !== true) {\\n throw new Error(\\n \\"fclt_init_operating_model force requires approve=true\\"\\n );\\n }\\n return [\\n \\"templates\\",\\n \\"init\\",\\n \\"operating-model\\",\\n ...scopeArgs(args.scope),\\n ...boolFlag(\\"--update\\", args.update),\\n ...(args.dryRun === false ? [] : [\\"--dry-run\\"]),\\n ...boolFlag(\\"--force\\", args.force),\\n \\"--json\\",\\n ];\\n case \\"fclt_writeback_add\\":\\n requireMutationApproval(name, args);\\n requireString(\\"evidence\\", args.evidence);\\n return [\\n \\"ai\\",\\n \\"writeback\\",\\n ...scopeArgs(args.scope),\\n \\"add\\",\\n \\"--kind\\",\\n args.kind,\\n \\"--summary\\",\\n args.summary,\\n ...stringFlag(\\"--asset\\", args.asset),\\n ...stringFlag(\\"--category\\", args.category),\\n ...stringFlag(\\"--details\\", args.details),\\n ...stringFlag(\\"--impact\\", args.impact),\\n ...stringFlag(\\"--attempted-workaround\\", args.attemptedWorkaround),\\n ...stringFlag(\\"--desired-outcome\\", args.desiredOutcome),\\n ...stringFlag(\\"--sensitivity\\", args.sensitivity),\\n ...stringFlag(\\"--evidence\\", args.evidence),\\n ...stringFlag(\\"--confidence\\", args.confidence),\\n \\"--json\\",\\n ];\\n case \\"fclt_writeback_review\\": {\\n const mode = args.mode || \\"list\\";\\n return [\\n \\"ai\\",\\n \\"writeback\\",\\n ...scopeArgs(args.scope),\\n mode,\\n ...stringFlag(\\"--by\\", args.by),\\n ];\\n }\\n case \\"fclt_evolve\\": {\\n const action = args.action || \\"list\\";\\n if ([\\"propose\\", \\"draft\\", \\"review\\"].includes(action)) {\\n requireMutationApproval(`fclt_evolve ${action}`, args);\\n }\\n if (action === \\"propose\\") {\\n requireString(\\"asset\\", args.asset);\\n }\\n if ([\\"draft\\", \\"review\\", \\"show\\"].includes(action)) {\\n requireString(\\"id\\", args.id);\\n }\\n return [\\n \\"ai\\",\\n \\"evolve\\",\\n ...scopeArgs(args.scope),\\n action,\\n ...(action === \\"assess\\" || action === \\"propose\\"\\n ? stringFlag(\\"--asset\\", args.asset)\\n : []),\\n ...(args.id ? [args.id] : []),\\n ...(action === \\"assess\\" ? [\\"--json\\"] : []),\\n ];\\n }\\n default:\\n throw new Error(`Unknown tool: ${name}`);\\n }\\n}\\n\\nfunction operationMetadata(name, args, command) {\\n const action =\\n name === \\"fclt_writeback_add\\"\\n ? \\"writeback_add\\"\\n : name === \\"fclt_evolve\\"\\n ? `evolve_${args.action || \\"list\\"}`\\n : args.action || name;\\n const reviewActions = new Set([\\n \\"activity_decide\\",\\n \\"writeback_add\\",\\n \\"writeback_link\\",\\n \\"writeback_disposition\\",\\n \\"evolve_propose\\",\\n \\"evolve_draft\\",\\n \\"evolve_review\\",\\n \\"evolve_verify\\",\\n \\"reconcile\\",\\n ]);\\n const preview = command.includes(\\"--dry-run\\");\\n const risk = reviewActions.has(action)\\n ? \\"review_producing\\"\\n : !preview && name === \\"fclt_setup\\"\\n ? \\"reversible_mutation\\"\\n : !preview && name === \\"fclt_init_operating_model\\"\\n ? \\"high_risk_destructive\\"\\n : \\"read_only\\";\\n return {\\n tool: name,\\n action,\\n risk,\\n scope:\\n args.scope ||\\n (name === \\"fclt_automation\\" && action === \\"loop_activity\\"\\n ? \\"all\\"\\n : \\"auto\\"),\\n target:\\n args.id ||\\n args.selector ||\\n args.asset ||\\n args.item ||\\n args.source ||\\n args.tool ||\\n args.locator ||\\n null,\\n preview,\\n };\\n}\\n\\nfunction recoveryForOperation(operation, stdout) {\\n if (operation.risk === \\"review_producing\\") {\\n return {\\n canonicalCapabilityChanged: false,\\n audit: \\"native fclt review artifacts and append-only journal\\",\\n };\\n }\\n if (\\n operation.risk !== \\"reversible_mutation\\" &&\\n operation.risk !== \\"high_risk_destructive\\"\\n ) {\\n return null;\\n }\\n const report = isPlainObject(stdout) ? stdout : {};\\n return {\\n rollbackAvailable: false,\\n changedPaths: Array.isArray(report.changedPaths) ? report.changedPaths : [],\\n skippedPaths: Array.isArray(report.skippedPaths) ? report.skippedPaths : [],\\n repairActions: Array.isArray(report.repairActions)\\n ? report.repairActions\\n : [],\\n verification:\\n operation.tool === \\"fclt_setup\\"\\n ? \\"rerun fclt_setup in preview mode and verify doctor readiness\\"\\n : \\"verify doctor, generated state, authored-file hashes, and exact target paths\\",\\n };\\n}\\n\\nasync function runFclt(args, cwd, operation) {\\n const discovery = await runtime.discoverRuntime();\\n if (!discovery.selected) {\\n return {\\n code: 1,\\n text: JSON.stringify(\\n {\\n schemaVersion: 1,\\n operation,\\n error: \\"no_compatible_runtime\\",\\n message:\\n \\"No compatible fclt runtime is available. Check, stage, and apply an explicit verified version with fclt_runtime.\\",\\n runtime: discovery,\\n },\\n null,\\n 2\\n ),\\n };\\n }\\n\\n if (\\n operation.tool === \\"fclt_audit\\" &&\\n !discovery.selected.capabilities?.includes(AUDIT_READ_ONLY_CAPABILITY)\\n ) {\\n return {\\n code: 1,\\n text: JSON.stringify(\\n {\\n schemaVersion: 1,\\n operation,\\n error: \\"missing_runtime_capability\\",\\n message:\\n \\"The selected fclt runtime does not advertise audit-read-only-v1; typed audit fails closed.\\",\\n runtime: discovery,\\n },\\n null,\\n 2\\n ),\\n };\\n }\\n\\n if (\\n operation.action === \\"activity_decide\\" &&\\n !discovery.selected.capabilities?.includes(\\n ACTIVITY_ACTION_DECIDE_CAPABILITY\\n )\\n ) {\\n return {\\n code: 1,\\n text: JSON.stringify(\\n {\\n schemaVersion: 1,\\n operation,\\n error: \\"missing_runtime_capability\\",\\n message:\\n \\"The selected fclt runtime does not advertise activity-action-decide-v1; typed activity decisions fail closed.\\",\\n runtime: discovery,\\n },\\n null,\\n 2\\n ),\\n };\\n }\\n\\n if (\\n operation.action === \\"activity_resolve\\" &&\\n !discovery.selected.capabilities?.includes(\\n ACTIVITY_ACTION_RESOLVE_CAPABILITY\\n )\\n ) {\\n return {\\n code: 1,\\n text: JSON.stringify(\\n {\\n schemaVersion: 1,\\n operation,\\n error: \\"missing_runtime_capability\\",\\n message:\\n \\"The selected fclt runtime does not advertise activity-action-resolve-v1; typed activity resolution fails closed.\\",\\n runtime: discovery,\\n },\\n null,\\n 2\\n ),\\n };\\n }\\n\\n const result = await runtime.runCommand(discovery.selected.executable, args, {\\n cwd: cwd || process.cwd(),\\n env: process.env,\\n timeoutMs: DEFAULT_TIMEOUT_MS,\\n });\\n const parsedStdout = parseJsonOrText(result.stdout.trim());\\n return {\\n code: result.code,\\n text: JSON.stringify(\\n {\\n schemaVersion: 1,\\n operation,\\n runtime: discovery.selected,\\n result: {\\n exitCode: result.code,\\n stdout: parsedStdout,\\n stderr: result.stderr,\\n },\\n verification: {\\n status: result.code === 0 ? \\"passed\\" : \\"failed\\",\\n exitCode: result.code,\\n },\\n recovery: recoveryForOperation(operation, parsedStdout),\\n },\\n null,\\n 2\\n ),\\n };\\n}\\n\\nfunction parseJsonOrText(value) {\\n if (!value) {\\n return \\"\\";\\n }\\n try {\\n return JSON.parse(value);\\n } catch {\\n return value;\\n }\\n}\\n\\nasync function handleRuntimeTool(args = {}) {\\n const action = args.action || \\"status\\";\\n if (action === \\"status\\") {\\n return await runtime.discoverRuntime();\\n }\\n if (action === \\"check\\") {\\n return await runtime.checkRuntimeUpdate();\\n }\\n if (action === \\"policy\\") {\\n return await runtime.setRuntimePolicy({\\n approve: args.approve,\\n pinnedVersion: args.pinnedVersion,\\n clearPin: args.clearPin,\\n updateChecksEnabled: args.updateChecksEnabled,\\n });\\n }\\n if (action === \\"stage\\") {\\n return await runtime.stageRuntime({\\n approve: args.approve,\\n version: args.version,\\n });\\n }\\n if (action === \\"apply\\") {\\n return await runtime.applyStagedRuntime({\\n approve: args.approve,\\n expectedSha256: args.expectedSha256,\\n version: args.version,\\n });\\n }\\n if (action === \\"rollback\\") {\\n return await runtime.rollbackRuntime({\\n approve: args.approve,\\n expectedActiveVersion: args.expectedActiveVersion,\\n });\\n }\\n throw new Error(`Unknown runtime action: ${action}`);\\n}\\n\\nfunction runtimeOperationMetadata(args, result) {\\n const action = args.action || \\"status\\";\\n const risk =\\n action === \\"status\\" || action === \\"check\\"\\n ? \\"read_only\\"\\n : action === \\"stage\\"\\n ? \\"review_producing\\"\\n : \\"high_risk_destructive\\";\\n return {\\n operation: {\\n tool: \\"fclt_runtime\\",\\n action,\\n risk,\\n scope: \\"plugin_runtime\\",\\n target:\\n args.version ||\\n args.pinnedVersion ||\\n args.expectedActiveVersion ||\\n null,\\n approved: args.approve === true,\\n },\\n verification: {\\n status: \\"passed\\",\\n activeVersion:\\n result.active?.packageVersion ||\\n result.selected?.packageVersion ||\\n null,\\n },\\n recovery:\\n action === \\"apply\\" || action === \\"rollback\\"\\n ? {\\n rollbackAvailable: result.rollbackAvailable === true,\\n previous: result.previous || null,\\n }\\n : action === \\"policy\\"\\n ? { previousPolicy: result.previous || null }\\n : null,\\n };\\n}\\n\\nlet transportFraming = \\"content-length\\";\\n\\nfunction send(message) {\\n const body = JSON.stringify(message);\\n if (transportFraming === \\"newline\\") {\\n process.stdout.write(`${body}\\\\n`);\\n return;\\n }\\n process.stdout.write(\\n `Content-Length: ${Buffer.byteLength(body)}\\\\r\\\\n\\\\r\\\\n${body}`\\n );\\n}\\n\\nasync function handle(message) {\\n if (!message || message.id == null) {\\n return;\\n }\\n\\n try {\\n if (message.method === \\"initialize\\") {\\n send({\\n jsonrpc: \\"2.0\\",\\n id: message.id,\\n result: {\\n protocolVersion: \\"2025-06-18\\",\\n capabilities: { tools: {} },\\n serverInfo: { name: \\"fclt\\", version: runtime.pluginVersion() },\\n },\\n });\\n return;\\n }\\n if (message.method === \\"tools/list\\") {\\n send({ jsonrpc: \\"2.0\\", id: message.id, result: { tools } });\\n return;\\n }\\n if (message.method === \\"tools/call\\") {\\n const { name, arguments: args = {} } = message.params || {};\\n validateToolArguments(name, args);\\n if (name === \\"fclt_runtime\\") {\\n const result = await handleRuntimeTool(args);\\n const metadata = runtimeOperationMetadata(args, result);\\n send({\\n jsonrpc: \\"2.0\\",\\n id: message.id,\\n result: {\\n isError: false,\\n content: [\\n {\\n type: \\"text\\",\\n text: JSON.stringify({ ...result, ...metadata }, null, 2),\\n },\\n ],\\n },\\n });\\n return;\\n }\\n const command = commandForTool(name, args);\\n const result = await runFclt(\\n command,\\n resolveToolCwd(name, args),\\n operationMetadata(name, args, command)\\n );\\n send({\\n jsonrpc: \\"2.0\\",\\n id: message.id,\\n result: {\\n isError: result.code !== 0,\\n content: [{ type: \\"text\\", text: result.text }],\\n },\\n });\\n return;\\n }\\n send({\\n jsonrpc: \\"2.0\\",\\n id: message.id,\\n error: { code: -32_601, message: `Method not found: ${message.method}` },\\n });\\n } catch (error) {\\n send({\\n jsonrpc: \\"2.0\\",\\n id: message.id,\\n error: {\\n code: -32_000,\\n message: error instanceof Error ? error.message : String(error),\\n },\\n });\\n }\\n}\\n\\nlet buffer = Buffer.alloc(0);\\n\\nfunction dispatch(body, framing) {\\n transportFraming = framing;\\n handle(JSON.parse(body)).catch((error) => {\\n send({\\n jsonrpc: \\"2.0\\",\\n id: null,\\n error: {\\n code: -32_000,\\n message: error instanceof Error ? error.message : String(error),\\n },\\n });\\n });\\n}\\n\\nprocess.stdin.on(\\"data\\", (chunk) => {\\n buffer = Buffer.concat([buffer, chunk]);\\n while (true) {\\n while (buffer[0] === 10 || buffer[0] === 13) {\\n buffer = buffer.subarray(1);\\n }\\n if (buffer.length === 0) {\\n return;\\n }\\n if (buffer[0] === 123 || buffer[0] === 91) {\\n const lineEnd = buffer.indexOf(\\"\\\\n\\");\\n if (lineEnd === -1) {\\n return;\\n }\\n const body = buffer.subarray(0, lineEnd).toString(\\"utf8\\").trim();\\n buffer = buffer.subarray(lineEnd + 1);\\n if (body) {\\n dispatch(body, \\"newline\\");\\n }\\n continue;\\n }\\n const headerEnd = buffer.indexOf(\\"\\\\r\\\\n\\\\r\\\\n\\");\\n if (headerEnd === -1) {\\n return;\\n }\\n const header = buffer.slice(0, headerEnd).toString(\\"utf8\\");\\n const match = CONTENT_LENGTH_RE.exec(header);\\n if (!match) {\\n buffer = Buffer.alloc(0);\\n return;\\n }\\n const length = Number(match[1]);\\n const frameEnd = headerEnd + 4 + length;\\n if (buffer.length < frameEnd) {\\n return;\\n }\\n const body = buffer.slice(headerEnd + 4, frameEnd).toString(\\"utf8\\");\\n buffer = buffer.slice(frameEnd);\\n dispatch(body, \\"content-length\\");\\n }\\n});\\n\\nif (process.argv.includes(\\"--self-test\\")) {\\n console.log(\\n JSON.stringify(\\n {\\n pluginVersion: runtime.pluginVersion(),\\n protocolVersion: runtime.PLUGIN_PROTOCOL_VERSION,\\n tools: tools.map((tool) => tool.name),\\n },\\n null,\\n 2\\n )\\n );\\n process.exit(0);\\n}\\n","scripts/fclt-runtime.cjs":"\\"use strict\\";\\n\\nconst { spawn } = require(\\"node:child_process\\");\\nconst crypto = require(\\"node:crypto\\");\\nconst fs = require(\\"node:fs\\");\\nconst fsp = require(\\"node:fs/promises\\");\\nconst https = require(\\"node:https\\");\\nconst os = require(\\"node:os\\");\\nconst path = require(\\"node:path\\");\\n\\nconst PLUGIN_PROTOCOL_VERSION = 1;\\nconst STATE_SCHEMA_VERSION = 1;\\nconst REPOSITORY = \\"hack-dance/fclt\\";\\nconst MAX_BINARY_BYTES = 256 * 1024 * 1024;\\nconst MAX_METADATA_BYTES = 2 * 1024 * 1024;\\nconst DOWNLOAD_TIMEOUT_MS = 30_000;\\nconst COMMAND_TIMEOUT_MS = 15_000;\\nconst ALLOWED_DOWNLOAD_HOSTS = new Set([\\n \\"api.github.com\\",\\n \\"github.com\\",\\n \\"objects.githubusercontent.com\\",\\n \\"release-assets.githubusercontent.com\\",\\n]);\\nconst SEMVER_RE = /^\\\\d+\\\\.\\\\d+\\\\.\\\\d+(?:[-+][0-9A-Za-z.-]+)?$/;\\nconst SHA256_RE = /^[a-f0-9]{64}$/;\\nconst NEWLINE_RE = /\\\\r?\\\\n/;\\nconst CHECKSUM_LINE_RE = /^([a-fA-F0-9]{64})\\\\s+\\\\*?(.+)$/;\\nconst WINDOWS_SHIM_RE = /\\\\.(?:bat|cmd)$/i;\\n\\nfunction isPlainObject(value) {\\n return Boolean(value) && typeof value === \\"object\\" && !Array.isArray(value);\\n}\\n\\nfunction pluginVersion() {\\n try {\\n const manifest = JSON.parse(\\n fs.readFileSync(\\n path.resolve(__dirname, \\"..\\", \\".codex-plugin\\", \\"plugin.json\\"),\\n \\"utf8\\"\\n )\\n );\\n return typeof manifest.version === \\"string\\" ? manifest.version : \\"unknown\\";\\n } catch {\\n return \\"unknown\\";\\n }\\n}\\n\\nfunction runtimeStateRoot(env = process.env, platform = process.platform) {\\n if (env.FCLT_PLUGIN_RUNTIME_DIR) {\\n return path.resolve(env.FCLT_PLUGIN_RUNTIME_DIR);\\n }\\n const home = env.HOME || env.USERPROFILE || os.homedir();\\n if (platform === \\"darwin\\") {\\n return path.join(\\n home,\\n \\"Library\\",\\n \\"Application Support\\",\\n \\"fclt\\",\\n \\"plugin-runtime\\"\\n );\\n }\\n if (platform === \\"win32\\") {\\n return path.join(\\n env.LOCALAPPDATA || path.join(home, \\"AppData\\", \\"Local\\"),\\n \\"fclt\\",\\n \\"plugin-runtime\\"\\n );\\n }\\n return path.join(\\n env.XDG_STATE_HOME || path.join(home, \\".local\\", \\"state\\"),\\n \\"fclt\\",\\n \\"plugin-runtime\\"\\n );\\n}\\n\\nfunction installStatePaths(env = process.env, platform = process.platform) {\\n const home = env.HOME || env.USERPROFILE || os.homedir();\\n const override = env.FACULT_LOCAL_STATE_DIR?.trim();\\n const portableRoot = override\\n ? path.resolve(override)\\n : platform === \\"darwin\\"\\n ? path.join(home, \\"Library\\", \\"Application Support\\", \\"fclt\\")\\n : path.join(\\n env.XDG_STATE_HOME\\n ? path.resolve(env.XDG_STATE_HOME)\\n : path.join(home, \\".local\\", \\"state\\"),\\n \\"fclt\\"\\n );\\n const candidates = [\\n path.join(portableRoot, \\"install.json\\"),\\n ...(platform === \\"win32\\"\\n ? [\\n path.join(\\n env.LOCALAPPDATA || path.join(home, \\"AppData\\", \\"Local\\"),\\n \\"fclt\\",\\n \\"install.json\\"\\n ),\\n ]\\n : []),\\n path.join(home, \\".ai\\", \\".facult\\", \\"install.json\\"),\\n path.join(home, \\".facult\\", \\"install.json\\"),\\n path.join(home, \\".local\\", \\"share\\", \\"fclt\\", \\"install.json\\"),\\n ];\\n return [...new Set(candidates.map((candidate) => path.resolve(candidate)))];\\n}\\n\\nfunction isSubpath(child, parent) {\\n const relative = path.relative(parent, child);\\n return (\\n relative === \\"\\" || !(relative.startsWith(\\"..\\") || path.isAbsolute(relative))\\n );\\n}\\n\\nasync function assertManagedPath(target, root) {\\n const resolvedRoot = path.resolve(root);\\n const resolvedTarget = path.resolve(target);\\n if (!isSubpath(resolvedTarget, resolvedRoot)) {\\n throw new Error(\\"Runtime path escapes the managed runtime root.\\");\\n }\\n\\n const relative = path.relative(resolvedRoot, path.dirname(resolvedTarget));\\n const segments = relative ? relative.split(path.sep) : [];\\n let cursor = resolvedRoot;\\n for (const segment of segments) {\\n cursor = path.join(cursor, segment);\\n try {\\n if ((await fsp.lstat(cursor)).isSymbolicLink()) {\\n throw new Error(\\"Runtime path traverses a symbolic link.\\");\\n }\\n } catch (error) {\\n if (error && error.code === \\"ENOENT\\") {\\n continue;\\n }\\n throw error;\\n }\\n }\\n return resolvedTarget;\\n}\\n\\nasync function readJson(pathValue) {\\n try {\\n const value = JSON.parse(await fsp.readFile(pathValue, \\"utf8\\"));\\n return isPlainObject(value) ? value : null;\\n } catch {\\n return null;\\n }\\n}\\n\\nasync function runtimePolicy(options = {}) {\\n const root = runtimeStateRoot(options.env, options.platform);\\n const persisted = await readJson(path.join(root, \\"policy.json\\"));\\n return {\\n schemaVersion: STATE_SCHEMA_VERSION,\\n updateChecksEnabled: persisted?.updateChecksEnabled !== false,\\n pinnedVersion:\\n typeof persisted?.pinnedVersion === \\"string\\" &&\\n persisted.pinnedVersion.trim()\\n ? normalizeVersion(persisted.pinnedVersion)\\n : null,\\n };\\n}\\n\\nfunction commandNames(platform = process.platform) {\\n return platform === \\"win32\\"\\n ? [\\"fclt.exe\\", \\"fclt.cmd\\", \\"facult.exe\\", \\"facult.cmd\\"]\\n : [\\"fclt\\", \\"facult\\"];\\n}\\n\\nfunction pathCandidates(env = process.env, platform = process.platform) {\\n const values = [];\\n for (const directory of (env.PATH || \\"\\").split(path.delimiter)) {\\n if (!directory) {\\n continue;\\n }\\n for (const name of commandNames(platform)) {\\n values.push(path.join(directory, name));\\n }\\n }\\n return values;\\n}\\n\\nfunction systemPathCandidates(env = process.env, platform = process.platform) {\\n if (Object.hasOwn(env, \\"FCLT_SYSTEM_PATHS\\")) {\\n return (env.FCLT_SYSTEM_PATHS || \\"\\").split(path.delimiter).filter(Boolean);\\n }\\n if (platform === \\"darwin\\") {\\n return [\\"/opt/homebrew/bin/fclt\\", \\"/usr/local/bin/fclt\\"];\\n }\\n if (platform === \\"win32\\") {\\n return [];\\n }\\n return [\\"/usr/local/bin/fclt\\", \\"/usr/bin/fclt\\"];\\n}\\n\\nfunction candidateSource(candidate) {\\n const normalized = candidate.split(\\"\\\\\\\\\\").join(\\"/\\");\\n if (normalized.includes(\\"/plugin-runtime/versions/\\")) {\\n return \\"plugin_runtime\\";\\n }\\n if (normalized.includes(\\"/mise/\\") || normalized.includes(\\"/mise/installs/\\")) {\\n return \\"mise\\";\\n }\\n if (\\n normalized.includes(\\"/Cellar/\\") ||\\n normalized.startsWith(\\"/opt/homebrew/\\")\\n ) {\\n return \\"homebrew\\";\\n }\\n if (normalized.includes(\\"/node_modules/\\") || normalized.includes(\\"/npm/\\")) {\\n return \\"npm\\";\\n }\\n if (normalized.includes(\\"/.ai/.facult/bin/\\")) {\\n return \\"canonical_install\\";\\n }\\n return \\"path\\";\\n}\\n\\nasync function activeRuntimeCandidate(root) {\\n const active = await readJson(path.join(root, \\"active.json\\"));\\n if (typeof active?.executable !== \\"string\\" || !active.executable.trim()) {\\n return null;\\n }\\n const executable = path.resolve(active.executable);\\n if (!isSubpath(executable, path.join(root, \\"versions\\"))) {\\n return null;\\n }\\n return {\\n executable,\\n source: \\"plugin_runtime\\",\\n expectedSha256: active.sha256,\\n active,\\n };\\n}\\n\\nasync function persistedInstallCandidates(\\n env = process.env,\\n platform = process.platform\\n) {\\n const candidates = [];\\n for (const statePath of installStatePaths(env, platform)) {\\n const state = await readJson(statePath);\\n if (typeof state?.binaryPath === \\"string\\" && state.binaryPath.trim()) {\\n candidates.push({\\n executable: path.resolve(state.binaryPath),\\n source:\\n typeof state.source === \\"string\\" ? state.source : \\"install_metadata\\",\\n installStatePath: statePath,\\n });\\n }\\n }\\n return candidates;\\n}\\n\\nasync function runtimeCandidates(options = {}) {\\n const env = options.env || process.env;\\n const platform = options.platform || process.platform;\\n const home = env.HOME || env.USERPROFILE || os.homedir();\\n const root = runtimeStateRoot(env, platform);\\n const candidates = [];\\n let configuredPathCandidate = null;\\n\\n if (env.FCLT_BIN?.trim()) {\\n const explicit = env.FCLT_BIN.trim();\\n if (path.isAbsolute(explicit) || explicit.includes(path.sep)) {\\n candidates.push({\\n executable: path.resolve(explicit),\\n source: \\"explicit\\",\\n });\\n } else {\\n const resolved = pathCandidates(env, platform).find(\\n (candidate) =>\\n path.basename(candidate) === explicit && fs.existsSync(candidate)\\n );\\n configuredPathCandidate = {\\n executable: resolved || explicit,\\n source: \\"configured_path\\",\\n };\\n }\\n }\\n\\n const active = await activeRuntimeCandidate(root);\\n if (active) {\\n candidates.push(active);\\n }\\n if (configuredPathCandidate) {\\n candidates.push(configuredPathCandidate);\\n }\\n candidates.push(...(await persistedInstallCandidates(env, platform)));\\n candidates.push(\\n ...pathCandidates(env, platform).map((executable) => ({\\n executable,\\n source: candidateSource(executable),\\n }))\\n );\\n\\n for (const executable of [\\n path.join(home, \\".ai\\", \\".facult\\", \\"bin\\", commandNames(platform)[0]),\\n ...systemPathCandidates(env, platform),\\n ]) {\\n candidates.push({ executable, source: candidateSource(executable) });\\n }\\n\\n const unique = [];\\n const seen = new Set();\\n for (const candidate of candidates) {\\n const key = path.resolve(candidate.executable);\\n if (!seen.has(key)) {\\n seen.add(key);\\n unique.push(candidate);\\n }\\n }\\n return unique;\\n}\\n\\nfunction runCommand(executable, args, options = {}) {\\n return new Promise((resolve) => {\\n let child;\\n try {\\n const platform = options.platform || process.platform;\\n const windowsShim =\\n platform === \\"win32\\" && WINDOWS_SHIM_RE.test(executable);\\n const command = windowsShim\\n ? options.env?.ComSpec || process.env.ComSpec || \\"cmd.exe\\"\\n : executable;\\n const commandArgs = windowsShim\\n ? [\\n \\"/d\\",\\n \\"/v:off\\",\\n \\"/s\\",\\n \\"/c\\",\\n [executable, ...args]\\n .map(\\n (value) =>\\n `\\"${String(value)\\n .replaceAll(\\"%\\", \\"%%\\")\\n .replace(/[\\\\^&|<>()!\\"]/g, \\"^$&\\")}\\"`\\n )\\n .join(\\" \\"),\\n ]\\n : args;\\n child = spawn(command, commandArgs, {\\n cwd: options.cwd || process.cwd(),\\n env: options.env || process.env,\\n stdio: [\\"ignore\\", \\"pipe\\", \\"pipe\\"],\\n });\\n } catch (error) {\\n resolve({ code: 1, stdout: \\"\\", stderr: error.message });\\n return;\\n }\\n let stdout = \\"\\";\\n let stderr = \\"\\";\\n let settled = false;\\n const timer = setTimeout(\\n () => child.kill(\\"SIGTERM\\"),\\n options.timeoutMs || COMMAND_TIMEOUT_MS\\n );\\n const finish = (code, error) => {\\n if (settled) {\\n return;\\n }\\n settled = true;\\n clearTimeout(timer);\\n resolve({\\n code,\\n stdout,\\n stderr: [stderr.trim(), error].filter(Boolean).join(\\"\\\\n\\"),\\n });\\n };\\n child.stdout.on(\\"data\\", (chunk) => {\\n stdout += chunk.toString();\\n if (stdout.length > MAX_METADATA_BYTES) {\\n child.kill(\\"SIGTERM\\");\\n }\\n });\\n child.stderr.on(\\"data\\", (chunk) => {\\n stderr += chunk.toString();\\n if (stderr.length > MAX_METADATA_BYTES) {\\n child.kill(\\"SIGTERM\\");\\n }\\n });\\n child.on(\\"error\\", (error) => finish(1, error.message));\\n child.on(\\"close\\", (code) => finish(code ?? 1));\\n });\\n}\\n\\nfunction parseProtocolReport(raw) {\\n try {\\n const report = JSON.parse(raw);\\n if (\\n !isPlainObject(report) ||\\n report.schemaVersion !== 1 ||\\n typeof report.packageVersion !== \\"string\\" ||\\n !isPlainObject(report.protocol) ||\\n !Number.isInteger(report.protocol.version) ||\\n !Number.isInteger(report.protocol.minimumPluginVersion) ||\\n !Number.isInteger(report.protocol.maximumPluginVersion) ||\\n (report.capabilities !== undefined &&\\n (!Array.isArray(report.capabilities) ||\\n report.capabilities.some((value) => typeof value !== \\"string\\")))\\n ) {\\n return null;\\n }\\n return report;\\n } catch {\\n return null;\\n }\\n}\\n\\nfunction protocolCompatibility(report) {\\n if (!report) {\\n return { compatible: false, reason: \\"missing_protocol_handshake\\" };\\n }\\n const compatible =\\n report.protocol.minimumPluginVersion <= PLUGIN_PROTOCOL_VERSION &&\\n report.protocol.maximumPluginVersion >= PLUGIN_PROTOCOL_VERSION;\\n return {\\n compatible,\\n reason: compatible ? \\"compatible\\" : \\"protocol_version_skew\\",\\n };\\n}\\n\\nasync function inspectCandidate(candidate, options = {}) {\\n const executable = path.resolve(candidate.executable);\\n try {\\n const stat = await fsp.stat(executable);\\n if (!stat.isFile()) {\\n return {\\n ...candidate,\\n executable,\\n available: false,\\n compatible: false,\\n reason: \\"not_a_file\\",\\n };\\n }\\n } catch {\\n return {\\n ...candidate,\\n executable,\\n available: false,\\n compatible: false,\\n reason: \\"not_found\\",\\n };\\n }\\n\\n if (candidate.source === \\"plugin_runtime\\") {\\n if (\\n typeof candidate.expectedSha256 !== \\"string\\" ||\\n !SHA256_RE.test(candidate.expectedSha256)\\n ) {\\n return {\\n ...candidate,\\n executable,\\n available: true,\\n compatible: false,\\n reason: \\"missing_checksum\\",\\n };\\n }\\n try {\\n const actualSha256 = sha256(await fsp.readFile(executable));\\n if (actualSha256 !== candidate.expectedSha256) {\\n return {\\n ...candidate,\\n executable,\\n available: true,\\n compatible: false,\\n reason: \\"checksum_mismatch\\",\\n };\\n }\\n } catch {\\n return {\\n ...candidate,\\n executable,\\n available: false,\\n compatible: false,\\n reason: \\"checksum_unreadable\\",\\n };\\n }\\n }\\n\\n const result = await runCommand(executable, [\\"protocol\\", \\"--json\\"], options);\\n const report =\\n result.code === 0 ? parseProtocolReport(result.stdout.trim()) : null;\\n const compatibility = protocolCompatibility(report);\\n return {\\n ...candidate,\\n executable,\\n available: true,\\n compatible: compatibility.compatible,\\n reason: compatibility.reason,\\n packageVersion: report?.packageVersion,\\n protocol: report?.protocol,\\n platform: report?.runtime?.platform,\\n architecture: report?.runtime?.architecture,\\n capabilities: report?.capabilities || [],\\n };\\n}\\n\\nasync function discoverRuntime(options = {}) {\\n const policy = await runtimePolicy(options);\\n const inspected = [];\\n for (const candidate of await runtimeCandidates(options)) {\\n const result = await inspectCandidate(candidate, options);\\n inspected.push(result);\\n if (result.compatible) {\\n return {\\n schemaVersion: STATE_SCHEMA_VERSION,\\n plugin: {\\n version: pluginVersion(),\\n protocolVersion: PLUGIN_PROTOCOL_VERSION,\\n },\\n policy,\\n selected: result,\\n compatible: true,\\n requiresFreshSession: false,\\n candidates: inspected,\\n };\\n }\\n }\\n return {\\n schemaVersion: STATE_SCHEMA_VERSION,\\n plugin: {\\n version: pluginVersion(),\\n protocolVersion: PLUGIN_PROTOCOL_VERSION,\\n },\\n policy,\\n selected: null,\\n compatible: false,\\n requiresFreshSession: false,\\n candidates: inspected,\\n };\\n}\\n\\nfunction releaseTarget(\\n platform = process.platform,\\n architecture = process.arch\\n) {\\n if (\\n platform === \\"darwin\\" &&\\n (architecture === \\"arm64\\" || architecture === \\"x64\\")\\n ) {\\n return { platform: \\"darwin\\", architecture, extension: \\"\\" };\\n }\\n if (platform === \\"linux\\" && architecture === \\"x64\\") {\\n return { platform: \\"linux\\", architecture, extension: \\"\\" };\\n }\\n if (platform === \\"win32\\" && architecture === \\"x64\\") {\\n return { platform: \\"windows\\", architecture, extension: \\".exe\\" };\\n }\\n throw new Error(\\n `Unsupported plugin runtime target: ${platform}/${architecture}`\\n );\\n}\\n\\nfunction normalizeVersion(version) {\\n const normalized = version?.startsWith(\\"v\\") ? version.slice(1) : version;\\n if (!(normalized && SEMVER_RE.test(normalized))) {\\n throw new Error(\\"Runtime version must be an explicit semantic version.\\");\\n }\\n return normalized;\\n}\\n\\nfunction assertAllowedUrl(urlValue) {\\n const url = new URL(urlValue);\\n if (url.protocol !== \\"https:\\" || !ALLOWED_DOWNLOAD_HOSTS.has(url.hostname)) {\\n throw new Error(\\n \\"Runtime downloads are restricted to approved HTTPS release hosts.\\"\\n );\\n }\\n return url;\\n}\\n\\nfunction downloadBuffer(urlValue, options = {}) {\\n const maxBytes = options.maxBytes || MAX_METADATA_BYTES;\\n const redirectsRemaining = options.redirectsRemaining ?? 5;\\n const url = assertAllowedUrl(urlValue);\\n return new Promise((resolve, reject) => {\\n const request = https.get(\\n url,\\n {\\n headers: {\\n accept: options.accept || \\"application/octet-stream\\",\\n \\"user-agent\\": \\"fclt-codex-plugin\\",\\n },\\n },\\n (response) => {\\n if (\\n response.statusCode &&\\n response.statusCode >= 300 &&\\n response.statusCode < 400 &&\\n response.headers.location\\n ) {\\n response.resume();\\n if (redirectsRemaining <= 0) {\\n reject(new Error(\\"Runtime download exceeded the redirect limit.\\"));\\n return;\\n }\\n const redirected = new URL(response.headers.location, url).toString();\\n downloadBuffer(redirected, {\\n ...options,\\n redirectsRemaining: redirectsRemaining - 1,\\n }).then(resolve, reject);\\n return;\\n }\\n if (response.statusCode !== 200) {\\n response.resume();\\n reject(\\n new Error(\\n `Runtime download failed with HTTP ${response.statusCode}.`\\n )\\n );\\n return;\\n }\\n const declaredLength = Number(response.headers[\\"content-length\\"] || 0);\\n if (declaredLength > maxBytes) {\\n response.resume();\\n reject(new Error(\\"Runtime download exceeds the allowed size.\\"));\\n return;\\n }\\n const chunks = [];\\n let total = 0;\\n response.on(\\"data\\", (chunk) => {\\n total += chunk.length;\\n if (total > maxBytes) {\\n request.destroy(\\n new Error(\\"Runtime download exceeds the allowed size.\\")\\n );\\n return;\\n }\\n chunks.push(chunk);\\n });\\n response.on(\\"end\\", () => resolve(Buffer.concat(chunks)));\\n }\\n );\\n request.setTimeout(options.timeoutMs || DOWNLOAD_TIMEOUT_MS, () => {\\n request.destroy(new Error(\\"Runtime download timed out.\\"));\\n });\\n request.on(\\"error\\", reject);\\n });\\n}\\n\\nfunction sha256(bytes) {\\n return crypto.createHash(\\"sha256\\").update(bytes).digest(\\"hex\\");\\n}\\n\\nfunction checksumForAsset(checksums, assetName) {\\n for (const line of checksums.split(NEWLINE_RE)) {\\n const match = CHECKSUM_LINE_RE.exec(line.trim());\\n if (match?.[2] === assetName) {\\n return match[1].toLowerCase();\\n }\\n }\\n throw new Error(`Published checksums do not include ${assetName}.`);\\n}\\n\\nasync function withMutationLock(root, action) {\\n await fsp.mkdir(root, { recursive: true, mode: 0o700 });\\n const lockPath = await assertManagedPath(\\n path.join(root, \\"mutation.lock\\"),\\n root\\n );\\n let handle;\\n try {\\n handle = await fsp.open(lockPath, \\"wx\\", 0o600);\\n } catch (error) {\\n if (error?.code === \\"EEXIST\\") {\\n throw new Error(\\n \\"Another fclt plugin runtime mutation is already in progress.\\"\\n );\\n }\\n throw error;\\n }\\n try {\\n return await action();\\n } finally {\\n await handle.close();\\n await fsp.rm(lockPath, { force: true });\\n }\\n}\\n\\nasync function writeJsonAtomic(pathValue, value, root) {\\n const target = await assertManagedPath(pathValue, root);\\n await fsp.mkdir(path.dirname(target), { recursive: true, mode: 0o700 });\\n const temporary = `${target}.tmp-${crypto.randomUUID()}`;\\n await fsp.writeFile(temporary, `${JSON.stringify(value, null, 2)}\\\\n`, {\\n mode: 0o600,\\n });\\n await fsp.rename(temporary, target);\\n}\\n\\nfunction releaseUrls(version, target) {\\n const tag = `v${version}`;\\n const assetName = `fclt-${version}-${target.platform}-${target.architecture}${target.extension}`;\\n const base = `https://github.com/${REPOSITORY}/releases/download/${tag}`;\\n return {\\n tag,\\n assetName,\\n binaryUrl: `${base}/${assetName}`,\\n checksumUrl: `${base}/SHA256SUMS`,\\n };\\n}\\n\\nfunction releaseMetadataUrl(version) {\\n return `https://api.github.com/repos/${REPOSITORY}/releases/tags/v${version}`;\\n}\\n\\nfunction releaseAssets(metadata, version, target) {\\n if (\\n !isPlainObject(metadata) ||\\n metadata.tag_name !== `v${version}` ||\\n !Array.isArray(metadata.assets)\\n ) {\\n throw new Error(\\n \\"Release metadata does not match the requested immutable tag.\\"\\n );\\n }\\n const expected = releaseUrls(version, target);\\n const findAsset = (name) =>\\n metadata.assets.find(\\n (asset) =>\\n isPlainObject(asset) &&\\n asset.name === name &&\\n typeof asset.browser_download_url === \\"string\\"\\n );\\n const binary = findAsset(expected.assetName);\\n const checksums = findAsset(\\"SHA256SUMS\\");\\n if (!(binary && checksums)) {\\n throw new Error(\\n \\"Release metadata is missing the required runtime or checksum asset.\\"\\n );\\n }\\n assertAllowedUrl(binary.browser_download_url);\\n assertAllowedUrl(checksums.browser_download_url);\\n return { binary, checksums, expected };\\n}\\n\\nfunction verifyPublishedDigest(asset, bytes) {\\n if (typeof asset.digest !== \\"string\\" || !asset.digest.trim()) {\\n return null;\\n }\\n const [algorithm, expected] = asset.digest.toLowerCase().split(\\":\\");\\n if (algorithm !== \\"sha256\\" || !SHA256_RE.test(expected || \\"\\")) {\\n throw new Error(`Release asset ${asset.name} has an unsupported digest.`);\\n }\\n const actual = sha256(bytes);\\n if (actual !== expected) {\\n throw new Error(\\n `Release asset ${asset.name} does not match its published digest.`\\n );\\n }\\n return asset.digest.toLowerCase();\\n}\\n\\nasync function resolveLatestVersion(fetchBuffer = downloadBuffer) {\\n const bytes = await fetchBuffer(\\n `https://api.github.com/repos/${REPOSITORY}/releases/latest`,\\n {\\n maxBytes: MAX_METADATA_BYTES,\\n accept: \\"application/vnd.github+json\\",\\n }\\n );\\n const metadata = JSON.parse(bytes.toString(\\"utf8\\"));\\n if (!isPlainObject(metadata) || typeof metadata.tag_name !== \\"string\\") {\\n throw new Error(\\"Latest release metadata did not include a tag.\\");\\n }\\n return normalizeVersion(metadata.tag_name);\\n}\\n\\nasync function checkRuntimeUpdate(options = {}) {\\n const discovery = await discoverRuntime(options);\\n if (!discovery.policy.updateChecksEnabled) {\\n return {\\n schemaVersion: STATE_SCHEMA_VERSION,\\n action: \\"check\\",\\n skipped: true,\\n reason: \\"update_checks_disabled\\",\\n currentVersion: discovery.selected?.packageVersion || null,\\n pinnedVersion: discovery.policy.pinnedVersion,\\n mutates: false,\\n };\\n }\\n const latestVersion =\\n discovery.policy.pinnedVersion ||\\n (await resolveLatestVersion(options.fetchBuffer || downloadBuffer));\\n return {\\n schemaVersion: STATE_SCHEMA_VERSION,\\n action: \\"check\\",\\n currentVersion: discovery.selected?.packageVersion || null,\\n latestVersion,\\n channel: discovery.policy.pinnedVersion ? \\"pinned\\" : \\"latest\\",\\n updateAvailable: discovery.selected?.packageVersion !== latestVersion,\\n selected: discovery.selected,\\n mutates: false,\\n };\\n}\\n\\nasync function stageRuntime(options) {\\n if (options.approve !== true) {\\n throw new Error(\\"Staging a runtime download requires approve=true.\\");\\n }\\n const version = normalizeVersion(options.version);\\n const target = releaseTarget(options.platform, options.architecture);\\n const root = runtimeStateRoot(options.env, options.platform);\\n const fetchBuffer = options.fetchBuffer || downloadBuffer;\\n const urls = releaseUrls(version, target);\\n const policy = await runtimePolicy(options);\\n if (policy.pinnedVersion && policy.pinnedVersion !== version) {\\n throw new Error(`Runtime policy is pinned to ${policy.pinnedVersion}.`);\\n }\\n\\n return await withMutationLock(root, async () => {\\n const metadataBytes = await fetchBuffer(releaseMetadataUrl(version), {\\n maxBytes: MAX_METADATA_BYTES,\\n accept: \\"application/vnd.github+json\\",\\n });\\n const metadata = JSON.parse(metadataBytes.toString(\\"utf8\\"));\\n const assets = releaseAssets(metadata, version, target);\\n const [checksumBytes, binaryBytes] = await Promise.all([\\n fetchBuffer(assets.checksums.browser_download_url, {\\n maxBytes: MAX_METADATA_BYTES,\\n }),\\n fetchBuffer(assets.binary.browser_download_url, {\\n maxBytes: MAX_BINARY_BYTES,\\n }),\\n ]);\\n const checksumDigest = verifyPublishedDigest(\\n assets.checksums,\\n checksumBytes\\n );\\n const binaryDigest = verifyPublishedDigest(assets.binary, binaryBytes);\\n const expectedSha256 = checksumForAsset(\\n checksumBytes.toString(\\"utf8\\"),\\n urls.assetName\\n );\\n const actualSha256 = sha256(binaryBytes);\\n if (expectedSha256 !== actualSha256) {\\n throw new Error(\\n \\"Downloaded runtime checksum does not match the published SHA256SUMS entry.\\"\\n );\\n }\\n\\n const stageDir = await assertManagedPath(\\n path.join(root, \\"staged\\", version),\\n root\\n );\\n await fsp.rm(stageDir, { recursive: true, force: true });\\n await fsp.mkdir(stageDir, { recursive: true, mode: 0o700 });\\n const executable = await assertManagedPath(\\n path.join(stageDir, target.platform === \\"windows\\" ? \\"fclt.exe\\" : \\"fclt\\"),\\n root\\n );\\n await fsp.writeFile(executable, binaryBytes, { mode: 0o700 });\\n if (target.platform !== \\"windows\\") {\\n await fsp.chmod(executable, 0o700);\\n }\\n\\n const inspected = await inspectCandidate(\\n { executable, source: \\"staged_plugin_runtime\\" },\\n { env: options.env, timeoutMs: options.timeoutMs }\\n );\\n if (!inspected.compatible || inspected.packageVersion !== version) {\\n await fsp.rm(stageDir, { recursive: true, force: true });\\n throw new Error(\\n \\"Staged runtime failed version or protocol verification.\\"\\n );\\n }\\n\\n const manifest = {\\n schemaVersion: STATE_SCHEMA_VERSION,\\n version,\\n tag: urls.tag,\\n assetName: urls.assetName,\\n executable,\\n sha256: actualSha256,\\n source: {\\n repository: REPOSITORY,\\n releaseMetadataUrl: releaseMetadataUrl(version),\\n binaryUrl: assets.binary.browser_download_url,\\n binaryAssetId: assets.binary.id ?? null,\\n binaryDigest,\\n checksumUrl: assets.checksums.browser_download_url,\\n checksumAssetId: assets.checksums.id ?? null,\\n checksumDigest,\\n },\\n protocol: inspected.protocol,\\n platform: target.platform,\\n architecture: target.architecture,\\n stagedAt: new Date().toISOString(),\\n };\\n await writeJsonAtomic(path.join(stageDir, \\"manifest.json\\"), manifest, root);\\n return { action: \\"stage\\", mutatesActiveRuntime: false, manifest };\\n });\\n}\\n\\nasync function verifyManifestExecutable(manifest, root, expectedParent) {\\n if (\\n !isPlainObject(manifest) ||\\n manifest.schemaVersion !== STATE_SCHEMA_VERSION ||\\n typeof manifest.version !== \\"string\\" ||\\n typeof manifest.executable !== \\"string\\" ||\\n typeof manifest.sha256 !== \\"string\\" ||\\n !SHA256_RE.test(manifest.sha256)\\n ) {\\n throw new Error(\\"Runtime manifest is missing required verification data.\\");\\n }\\n const executable = path.resolve(manifest.executable);\\n if (!isSubpath(executable, expectedParent)) {\\n throw new Error(\\n \\"Runtime manifest executable escapes its expected directory.\\"\\n );\\n }\\n await assertManagedPath(executable, root);\\n const bytes = await fsp.readFile(executable);\\n if (sha256(bytes) !== manifest.sha256) {\\n throw new Error(\\"Runtime manifest checksum does not match its executable.\\");\\n }\\n const inspected = await inspectCandidate({\\n executable,\\n source: \\"plugin_runtime\\",\\n expectedSha256: manifest.sha256,\\n });\\n if (!inspected.compatible || inspected.packageVersion !== manifest.version) {\\n throw new Error(\\n \\"Runtime manifest executable failed protocol verification.\\"\\n );\\n }\\n return inspected;\\n}\\n\\nasync function setRuntimePolicy(options = {}) {\\n if (options.approve !== true) {\\n throw new Error(\\"Changing runtime update policy requires approve=true.\\");\\n }\\n const root = runtimeStateRoot(options.env, options.platform);\\n return await withMutationLock(root, async () => {\\n const current = await runtimePolicy(options);\\n const next = {\\n schemaVersion: STATE_SCHEMA_VERSION,\\n updateChecksEnabled:\\n typeof options.updateChecksEnabled === \\"boolean\\"\\n ? options.updateChecksEnabled\\n : current.updateChecksEnabled,\\n pinnedVersion: options.clearPin\\n ? null\\n : options.pinnedVersion\\n ? normalizeVersion(options.pinnedVersion)\\n : current.pinnedVersion,\\n updatedAt: new Date().toISOString(),\\n };\\n await writeJsonAtomic(path.join(root, \\"policy.json\\"), next, root);\\n return { action: \\"policy\\", previous: current, policy: next };\\n });\\n}\\n\\nasync function applyStagedRuntime(options) {\\n if (options.approve !== true) {\\n throw new Error(\\"Applying a runtime requires approve=true.\\");\\n }\\n const version = normalizeVersion(options.version);\\n const root = runtimeStateRoot(options.env, options.platform);\\n return await withMutationLock(root, async () => {\\n const stageDir = path.join(root, \\"staged\\", version);\\n const manifest = await readJson(path.join(stageDir, \\"manifest.json\\"));\\n if (!manifest) {\\n throw new Error(`No staged runtime exists for ${version}.`);\\n }\\n if (options.expectedSha256 !== manifest.sha256) {\\n throw new Error(\\n \\"Staged runtime precondition failed: expected checksum changed.\\"\\n );\\n }\\n await verifyManifestExecutable(manifest, root, stageDir);\\n\\n const activePath = path.join(root, \\"active.json\\");\\n const previous = await readJson(activePath);\\n const versionDir = await assertManagedPath(\\n path.join(root, \\"versions\\", version),\\n root\\n );\\n await fsp.mkdir(versionDir, { recursive: true, mode: 0o700 });\\n const executable = await assertManagedPath(\\n path.join(versionDir, path.basename(manifest.executable)),\\n root\\n );\\n const temporary = `${executable}.tmp-${crypto.randomUUID()}`;\\n await fsp.copyFile(manifest.executable, temporary);\\n if (process.platform !== \\"win32\\") {\\n await fsp.chmod(temporary, 0o700);\\n }\\n await fsp.rename(temporary, executable);\\n const activeManifest = {\\n ...manifest,\\n executable,\\n activatedAt: new Date().toISOString(),\\n previous:\\n typeof previous?.version === \\"string\\" &&\\n typeof previous?.executable === \\"string\\"\\n ? {\\n version: previous.version,\\n executable: previous.executable,\\n sha256: previous.sha256,\\n }\\n : null,\\n };\\n await writeJsonAtomic(activePath, activeManifest, root);\\n const inspected = await verifyManifestExecutable(\\n activeManifest,\\n root,\\n versionDir\\n );\\n return {\\n action: \\"apply\\",\\n active: inspected,\\n previous: activeManifest.previous,\\n rollbackAvailable: Boolean(activeManifest.previous),\\n requiresFreshSession: false,\\n };\\n });\\n}\\n\\nasync function rollbackRuntime(options = {}) {\\n if (options.approve !== true) {\\n throw new Error(\\"Rolling back a runtime requires approve=true.\\");\\n }\\n const root = runtimeStateRoot(options.env, options.platform);\\n return await withMutationLock(root, async () => {\\n const activePath = path.join(root, \\"active.json\\");\\n const active = await readJson(activePath);\\n if (!isPlainObject(active?.previous)) {\\n throw new Error(\\n \\"The active plugin runtime does not have a retained rollback target.\\"\\n );\\n }\\n if (\\n options.expectedActiveVersion &&\\n options.expectedActiveVersion !== active.version\\n ) {\\n throw new Error(\\n \\"Runtime rollback precondition failed: active version changed.\\"\\n );\\n }\\n const previous = {\\n schemaVersion: STATE_SCHEMA_VERSION,\\n version: active.previous.version,\\n executable: active.previous.executable,\\n sha256: active.previous.sha256,\\n previous: {\\n version: active.version,\\n executable: active.executable,\\n sha256: active.sha256,\\n },\\n rolledBackAt: new Date().toISOString(),\\n };\\n const previousDir = path.dirname(path.resolve(previous.executable));\\n const inspected = await verifyManifestExecutable(\\n previous,\\n root,\\n previousDir\\n );\\n await writeJsonAtomic(activePath, previous, root);\\n return {\\n action: \\"rollback\\",\\n active: inspected,\\n rolledBackFrom: active.version,\\n rollbackAvailable: true,\\n requiresFreshSession: false,\\n };\\n });\\n}\\n\\nmodule.exports = {\\n PLUGIN_PROTOCOL_VERSION,\\n applyStagedRuntime,\\n assertManagedPath,\\n checkRuntimeUpdate,\\n checksumForAsset,\\n discoverRuntime,\\n downloadBuffer,\\n normalizeVersion,\\n parseProtocolReport,\\n pluginVersion,\\n protocolCompatibility,\\n releaseTarget,\\n rollbackRuntime,\\n runCommand,\\n runtimeCandidates,\\n runtimePolicy,\\n runtimeStateRoot,\\n setRuntimePolicy,\\n sha256,\\n stageRuntime,\\n};\\n","skills/fclt-capability-review/SKILL.md":"---\\nname: fclt-capability-review\\ndescription: Inspect fclt capability roots, docs, snippets, skills, agents, MCP, and automations.\\ntags: [fclt, capability, review, inventory]\\n---\\n\\n# fclt-capability-review\\n\\n## When To Use\\nUse this skill when Codex needs to understand what capability exists before changing it.\\n\\nUse it for:\\n\\n- checking global and project `.ai` roots\\n- finding relevant skills, snippets, instructions, agents, MCP servers, or automations\\n- deciding whether a change belongs in global or project scope\\n- checking whether managed rendering is enabled or needed\\n- reviewing public/private boundaries before publishing docs or pack assets\\n\\n## Workflow\\n\\n```bash\\nfclt status --json\\nfclt inventory --json\\nfclt list skills\\nfclt list instructions\\nfclt list snippets\\nfclt graph AGENTS.global.md\\n```\\n\\nFor project work:\\n\\n```bash\\nfclt status --project --json\\nfclt inventory --project --json\\n```\\n\\n## Rules\\n\\n- Read existing repo guidance before proposing project capability.\\n- Use project scope for repo-specific commands, tests, architecture, or team workflow.\\n- Use global scope only for broadly reusable behavior.\\n- Keep generated state and review artifacts out of repo-local `.ai`.\\n- Prefer adding or updating the smallest unit: instruction, snippet, skill, agent, MCP config, or automation.\\n- Treat engineering implementation as task work. Do not force product changes\\n through capability evolution merely because evolution tools exist.\\n- Before recommending mutation, state the observed problem, evidence, why the\\n proposed capability is the correct target, risk class, expected outcome,\\n verification plan, assumptions, and recovery route.\\n- Never silently overwrite authored capability or confuse generated/rendered\\n output with canonical source.\\n\\n## Output\\n\\n- capability roots found\\n- relevant assets\\n- scope recommendation\\n- missing or stale capability\\n- safe next command\\n- deliberately withheld operations or missing safer API\\n- verification and undo path for any proposed mutation\\n\\n## Authorization and evidence boundaries\\n\\nUse existing authorization for the same action and scope; do not request the same approval again.\\nHonor read-only and proposal-only requests by returning findings or a proposed writeback without\\nrecording state. When review-state mutation is authorized, prepare the smallest evidence-backed draft.\\nDrafting or recording does not authorize canonical apply, cross-scope promotion, external messages,\\ntracker changes, publishing, or scheduler activation. Preserve those gates and native authorization.\\nDo not infer effectiveness from apply: verify the producing workflow before resolving source writebacks.\\nKeep unchanged, non-actionable scheduled results quiet unless periodic reports were requested.\\n\\nFor a versioned capability registry, use `fclt capability inventory` with explicit source/target/state roots and host/platform overlays. Review source pins, ownership, drift, credential-reference availability, and retained disabled targets. Independent native records describe intent, not observed version or readiness. A successful file deployment does not prove provider discovery or execution. Preserve both sides of a conflict; do not repair it with an upstream installer in a managed output directory.\\n","skills/fclt-evolution/SKILL.md":"---\\nname: fclt-evolution\\ndescription: Turn repeated fclt writebacks into reviewed capability changes.\\ntags: [fclt, evolution, proposals, capability]\\n---\\n\\n# fclt-evolution\\n\\n## When To Use\\nUse this skill when repeated writebacks, stale canonical assets, or a clearly missing capability should become a concrete proposal.\\n\\nDo not use it for a single weak preference or speculative idea.\\n\\n## Authorization\\n\\nProceed with recording or drafting when the current request authorizes that action and scope;\\ndo not request the same approval again. For read-only or proposal-only work, return findings or a\\nproposed writeback without changing state. Strong evidence alone does not grant write authority.\\n\\nRecording or drafting does not authorize canonical application, cross-scope promotion, external\\nmessages, tracker changes, publishing, or scheduler activation. Preserve those separate gates and\\nnative authorization. Keep unchanged, non-actionable scheduled results quiet unless periodic\\nreports were requested.\\n\\n## Workflow\\n\\n1. Review signal:\\n\\n```bash\\nfclt ai review reconcile --since --until --json\\nfclt ai writeback group --by asset\\nfclt ai writeback summarize --by domain\\nfclt ai evolve list\\n```\\n\\nFor an enabled scheduled loop, inspect its durable queue and observed scheduler\\nhealth with `fclt ai loop status --json`. Use `fclt ai loop run --dry-run\\n--json` for a fresh incremental scan that does not advance cursors or write\\nreconciliation or loop state. The full queue is authoritative; the notification\\ndelta intentionally suppresses unchanged items.\\n\\n2. Assess proposal readiness before mutating state:\\n\\n```bash\\nfclt ai evolve assess --asset --json\\n```\\n\\nUse the assessment recommendation as the decision checkpoint:\\n\\n- `reconcile_sources`: run the bounded read-only source review; writebacks alone cannot prove the window is empty.\\n- `review_reconciled_signals`: review correlated dispositions and linked work without creating one proposal per ticket.\\n- `no_mutation`: do not change capability state; ask for a target or evidence.\\n- `record_more_writeback`: explain what recurrence would justify evolution and record a new writeback only if there is fresh concrete evidence.\\n- `propose`: create the smallest target-specific draft when review-state mutation is already authorized; otherwise present the proposed action. Retain canonical apply and promotion gates.\\n- `review_existing_proposal`: inspect or revise the existing proposal instead of creating a duplicate.\\n\\n3. Propose only when evidence is strong enough:\\n\\n```bash\\nfclt ai evolve propose\\n```\\n\\n4. Draft and inspect:\\n\\n```bash\\nfclt ai evolve draft EV-00001\\nfclt ai evolve review EV-00001\\n```\\n\\n5. Present the reviewed proposal with its exact target, scope, evidence, and required authority.\\nCanonical apply and cross-scope promotion are unavailable through this plugin. Stop at the\\nreviewed proposal; do not use shell commands to bypass the unavailable surface.\\n\\n6. Verify the outcome after the producing loop has had a real chance to run:\\n\\n```bash\\nfclt ai writeback link WB-00001 --issue TEAM-123\\nfclt ai writeback disposition WB-00001 --type task --target TEAM-123\\nfclt ai evolve verify EV-00001 --effectiveness improved --evidence test:post-apply\\n```\\n\\nApply is not completion. Do not resolve source writebacks until post-apply evidence shows the\\nintended behavior improved. Treat recurrence as unchanged or regressed evidence linked to the same\\nevolution, not as an unrelated singleton.\\n\\n## Proposal Kinds\\n\\n- `update_asset`\\n- `create_asset`\\n- `extract_snippet`\\n- `add_skill`\\n- `promote_asset`\\n\\n## Rules\\n\\n- Prefer the smallest valid proposal kind.\\n- Keep project-specific behavior project-scoped until reuse is proven.\\n- Require approval covering the exact scope before applying global instructions, global skills, plugin behavior, or other broad shared surfaces. Existing approval for that action and scope is sufficient; never bypass an unavailable or restricted apply surface.\\n- Reject or park proposals that are stale, duplicated, vague, or unsupported.\\n- Use the operator\'s task system for executable implementation work that needs owner, priority, or state.\\n- A no-op answer must still be useful: include the evidence grade, missing signal, next writeback target, and exact approval boundary.\\n- State the problem, source evidence, reason for the selected target, risk,\\n expected outcome, verification plan, assumptions, and undo path before any\\n review-producing or mutating action.\\n- Preview and verify the exact scope. Never overwrite authored capability\\n silently or treat generated/rendered output as the canonical target.\\n- Canonical apply and cross-scope promotion are unavailable through the plugin\\n until a transaction-safe API can return tested rollback data. Do not bypass\\n that boundary with shell or arbitrary CLI arguments.\\n- The plugin exposes only closed-schema loop status and preview actions.\\n Scheduler enable/disable/run and external tracker mutation remain outside the\\n MCP surface.\\n\\n## Output\\n\\n- proposals reviewed\\n- repeated signal\\n- assessment recommendation\\n- proposal created or updated\\n- approvals needed\\n- apply/reject/no-op rationale\\n- actual changed records/artifacts, verification result, and recovery route\\n","skills/fclt-setup/SKILL.md":"---\\nname: fclt-setup\\ndescription: Install, update, inspect, and initialize fclt from Codex.\\ntags: [fclt, setup, codex, onboarding]\\n---\\n\\n# fclt-setup\\n\\n## When To Use\\nUse this skill when a user wants Codex to install, update, configure, inspect, or repair fclt.\\n\\nUse it for:\\n\\n- checking whether `fclt` is installed and current\\n- initializing global `~/.ai`\\n- discovering repositories and enrolling a reviewed minimal project layer\\n- installing or refreshing the built-in operating-model pack\\n- checking setup health with `doctor`\\n- finding canonical, generated, runtime, and review paths\\n\\n## Workflow\\n\\n1. For inspection, start with `fclt --version`, `fclt paths --json`, and `fclt doctor --json`. Run setup only when initialization or repair is authorized; existing authorization for the same targets is sufficient. Bootstrap an authorized global setup with:\\n\\n```bash\\nfclt setup\\n```\\n\\nThis initializes or safely updates global capability, prepares writeback/evolution review state,\\nand installs the Codex plugin when Codex is available. It does not initialize the current\\nrepository.\\n\\n2. Inspect runtime selection and compatibility with `fclt_runtime` action\\n `status`. Report the selected executable, version, source, protocol\\n compatibility, and fresh-session state.\\n\\nIf no compatible runtime is available, use the staged lifecycle:\\n\\n- `check` is read-only\\n- `stage` requires an explicit version and approval, but does not activate it\\n- `apply` requires approval plus the staged checksum precondition\\n- `rollback` verifies and restores the retained prior runtime\\n\\nNever curl-pipe code, use an unverified mutable URL, or replace an existing\\nglobal installation silently.\\n\\n3. Check current setup state and exact repair actions:\\n\\n```bash\\nfclt --version\\nfclt paths --json\\nfclt doctor --json\\n```\\n\\nThrough MCP, call `fclt_setup` with an explicit `global` or\\n`global_and_project` scope. Project setup also requires the exact `cwd`.\\nPreview is the default; apply requires both `dryRun: false` and\\n`approve: true`. `global_and_project` returns a no-write project enrollment\\nplan; project application still uses the typed CLI plan-hash contract below.\\n\\n4. For advanced manual recovery, initialize global capability when missing:\\n\\n```bash\\nfclt templates init operating-model --global\\n```\\n\\n5. Discover candidate repositories only beneath explicit roots:\\n\\n```bash\\nfclt projects discover --root ~/dev --since 30d --json\\n```\\n\\nDiscovery is bounded and read-only. Review duplicate clone/worktree groups,\\ndirty state, existing guidance, stable portfolio identity, and the separate\\ncheckout/worktree execution identity before selecting a project. Never\\nbulk-enroll the discovery result.\\n\\n6. Preview the exact minimal project enrollment plan:\\n\\n```bash\\nfclt project init --project-root /path/to/repo --json\\n```\\n\\nThe minimal layer is `.ai/.gitignore` plus `.ai/config.toml`. It does not\\ninstall the operating pack, enable managed rendering, schedule a loop, or copy\\n`AGENTS.md`/`CLAUDE.md`. Review every canonical, generated, and machine-local\\nwrite plus the rollback command.\\n\\nIf project guidance should be adopted, name each canonical file explicitly:\\n\\n```bash\\nfclt project init --project-root /path/to/repo \\\\\\n --guidance AGENTS.md --json\\n```\\n\\nGuidance adoption is reference-only. fclt previews the full content and hash,\\nand refuses untracked, modified, `assume-unchanged`, `skip-worktree`,\\nsecret-shaped, or machine-path-bearing input. Cleanliness requires identical\\nworktree, index, and `HEAD` blobs.\\n\\n7. Apply only the unchanged reviewed plan:\\n\\n```bash\\nfclt project init --project-root /path/to/repo \\\\\\n --apply --plan-sha --json\\n```\\n\\nIf options, source files, or preconditions change, discard the old hash and\\npreview again. Apply serializes portfolio registry mutations, isolates\\nlocation-bearing generated state per checkout/worktree, refuses symlinked\\ngenerated targets, and publishes the receipt only after the registry commit.\\n\\n8. Inspect health, coverage, and lifecycle:\\n\\n```bash\\nfclt projects status --root /path/to/repo --json\\nfclt project disable --project-root /path/to/repo --json\\nfclt project rollback --receipt --json\\n```\\n\\nDisable and remove decisions preserve canonical files, receipts, and review\\nhistory. Rollback previews by default and refuses drift.\\n\\n9. Install the full operating pack only when explicitly requested:\\n\\n```bash\\nfclt templates init operating-model --project --dry-run\\nfclt templates init operating-model --project\\n```\\n\\nThis is distinct from minimal enrollment and does not seed project\\n`AGENTS.global.md` from repository guidance.\\n\\n10. Refresh global pack defaults non-destructively:\\n\\n```bash\\nfclt templates init operating-model --global --update --dry-run\\nfclt templates init operating-model --global --update\\n```\\n\\n11. Use `--force` only when the user explicitly wants to replace local edits.\\n\\n## Rules\\n\\n- Preserve existing `AGENTS.md`, `CLAUDE.md`, and `AGENTS.global.md` guidance.\\n- Never infer guidance adoption from filenames or copy repository guidance into\\n `.ai/AGENTS.global.md`.\\n- Treat root `AGENTS.md` or `CLAUDE.md` as canonical repository guidance unless\\n the user explicitly chooses another tracked, clean source.\\n- Write the protective `.ai/.gitignore` before generated state.\\n- Keep canonical project files separate from machine-local generated indexes,\\n registries, receipts, and scheduling state.\\n- Keep minimal enrollment separate from the full operating pack and managed\\n rendering.\\n- Treat `doctor --json` issues as setup facts, not user-facing blame.\\n- Treat Codex plugin registration as weaker evidence than fresh-session tool discovery.\\n- Treat external trackers as separate integrations. Core readiness depends only on configured local evidence coverage, not a vendor plugin or token.\\n- Prefer temp-root smoke tests for install/update behavior.\\n- Do not enable managed rendering unless the user wants fclt to write tool homes.\\n- Preview before mutation and state the exact global/project/plugin target.\\n- Do not report a staged runtime or installed plugin as active until the active\\n handshake and fresh-session discovery have been verified.\\n\\n## Output\\n\\n- current installed version\\n- setup health\\n- paths that matter\\n- commands run\\n- what changed\\n- problem, evidence, reason, target, risk, and expected outcome\\n- verification performed and its actual result\\n- assumptions and fresh-session state\\n- exact undo or rollback path\\n- what still needs approval\\n\\n## Versioned skill and plugin sources\\n\\nFor registered skill, custom plugin, or MCP artifact updates, use `fclt capability inventory` and a per-entry `plan` before `apply --id ... --expected-plan ...`. Use the registry\'s explicit source, target, and runtime state roots; pass machine/private choices with ordered `--overlay` files. Acquire upstream updates into isolated staging, review the snapshot, and record its revision and `capability digest` before deploying. Do not run embedded installer or auto-update instructions against fclt-owned outputs, and do not use broad legacy sync to repair drift. Preserve divergent files for source selection.\\n\\nNative plugin managers own their installed files. Registered native versions are intended versions until verified with the provider. `enabled: false` stops fclt management but retains existing files; it is not a provider disable or uninstall operation. See the package\'s `docs/capability-registry.md` for the schema, conflict states, and recovery procedure.\\n","skills/fclt-writeback/SKILL.md":"---\\nname: fclt-writeback\\ndescription: Record and review fclt writebacks from real agent work.\\ntags: [fclt, writeback, learning, feedback-loop]\\n---\\n\\n# fclt-writeback\\n\\n## When To Use\\nUse this skill when work reveals durable friction, missing context, weak verification, stale guidance, repeated success, or a capability gap.\\n\\nWriteback is for preserving signal. It is not for every preference or one-off annoyance.\\n\\n## Authorization\\n\\nProceed with recording or drafting when the current request authorizes that action and scope;\\ndo not request the same approval again. For read-only or proposal-only work, return findings or a\\nproposed writeback without changing state. Strong evidence alone does not grant write authority.\\n\\nRecording or drafting does not authorize canonical application, cross-scope promotion, external\\nmessages, tracker changes, publishing, or scheduler activation. Preserve those separate gates and\\nnative authorization. Keep unchanged, non-actionable scheduled results quiet unless periodic\\nreports were requested.\\n\\n## Workflow\\n\\n1. Decide scope:\\n\\n- `project` when the learning depends on a repo, test harness, architecture, or workflow.\\n- `global` when the learning applies across projects or shared tool behavior.\\n\\n2. Choose the smallest target:\\n\\n- instruction\\n- snippet\\n- skill\\n- agent\\n- MCP/tool config\\n- automation\\n\\n3. Record writeback when the target, evidence, and authorization are clear:\\n\\n```bash\\nfclt ai writeback add \\\\\\n --kind missing_context \\\\\\n --category opportunity \\\\\\n --summary \\"Project verification guidance was not discoverable\\" \\\\\\n --details \\"The task had to reconstruct the command from CI configuration\\" \\\\\\n --impact \\"Verification took longer and could have selected the wrong harness\\" \\\\\\n --attempted-workaround \\"Inspected package scripts and CI\\" \\\\\\n --desired-outcome \\"The supported verification command is available at task start\\" \\\\\\n --sensitivity internal \\\\\\n --evidence session: \\\\\\n --asset @project/instructions/TESTING.md\\n```\\n\\n4. Review current signal:\\n\\n```bash\\nfclt ai writeback list\\nfclt ai writeback group --by asset\\nfclt ai writeback summarize --by domain\\nfclt ai loop activity --project\\n```\\n\\n## Rules\\n\\n- Prefer one high-signal writeback over several weak ones.\\n- Include concrete evidence when possible.\\n- Capture concise context, impact, attempted workaround, desired outcome, and\\n sensitivity when they improve review quality.\\n- Never capture hidden chain-of-thought, raw transcripts, unbounded logs,\\n secrets, tokens, or credential-bearing payloads. Reference the smallest\\n redacted external evidence identifier instead.\\n- Do not copy private project detail into global writebacks.\\n- Use task tracking for executable product/tooling work; use writeback for reusable operating-layer learning.\\n- If the same signal repeats and the target is clear, hand off to `fclt-evolution`.\\n- State the observed problem, evidence, target, reason, expected outcome, and\\n assumptions before recording.\\n- Do not capture secrets, private tokens, or raw sensitive payloads as evidence.\\n- For lifecycle mutations, use an explicit scope and expected prior state.\\n Report the journal/review evidence and the available undo transition.\\n\\n## Output\\n\\n- writeback id or no-op rationale\\n- scope\\n- target asset\\n- evidence summary\\n- whether this is ready for evolution\\n- risk class and approval boundary\\n- actual changed records/artifacts\\n- verification result and recovery route\\n\\n\\nVerify the producing workflow before resolving source writebacks; applying a change alone does not prove effectiveness.\\n"}' + '{".codex-plugin/plugin.json":"{\\n \\"name\\": \\"fclt\\",\\n \\"version\\": \\"0.1.6\\",\\n \\"description\\": \\"Codex workflows and MCP tools for evidence reconciliation, writeback, evolution, and outcome review.\\",\\n \\"author\\": {\\n \\"name\\": \\"Hack Dance\\",\\n \\"url\\": \\"https://hack.dance\\"\\n },\\n \\"license\\": \\"MIT\\",\\n \\"keywords\\": [\\n \\"fclt\\",\\n \\"facult\\",\\n \\"codex\\",\\n \\"skills\\",\\n \\"mcp\\",\\n \\"writeback\\",\\n \\"evolution\\"\\n ],\\n \\"skills\\": \\"./skills/\\",\\n \\"mcpServers\\": \\"./.mcp.json\\",\\n \\"interface\\": {\\n \\"displayName\\": \\"fclt\\",\\n \\"shortDescription\\": \\"Feedback loops for AI work\\",\\n \\"longDescription\\": \\"Capture signal from real agent work, reconcile configured evidence, turn repeated findings into reviewed capability changes, and verify whether those changes improved the work that produced them.\\",\\n \\"developerName\\": \\"Hack Dance\\",\\n \\"category\\": \\"Productivity\\",\\n \\"capabilities\\": [\\"Read\\", \\"Write\\", \\"MCP\\"],\\n \\"defaultPrompt\\": [\\n \\"Use fclt to check this repo\'s AI capability setup.\\",\\n \\"Reconcile the configured evidence window and explain its coverage.\\",\\n \\"Record useful writeback from this work and review any repeated signal.\\"\\n ],\\n \\"brandColor\\": \\"#166534\\",\\n \\"composerIcon\\": \\"./assets/fclt-mark.png\\",\\n \\"logo\\": \\"./assets/fclt-mark.png\\"\\n }\\n}\\n",".mcp.json":"{\\n \\"mcpServers\\": {\\n \\"fclt\\": {\\n \\"command\\": \\"node\\",\\n \\"args\\": [\\"./scripts/fclt-mcp.cjs\\"],\\n \\"env\\": {\\n \\"FCLT_BIN\\": \\"fclt\\"\\n },\\n \\"cwd\\": \\".\\"\\n }\\n }\\n}\\n","scripts/fclt-mcp.cjs":"#!/usr/bin/env node\\n\\"use strict\\";\\n\\nconst fs = require(\\"node:fs\\");\\nconst os = require(\\"node:os\\");\\nconst path = require(\\"node:path\\");\\nconst runtime = require(\\"./fclt-runtime.cjs\\");\\n\\nconst DEFAULT_TIMEOUT_MS = Number(process.env.FCLT_MCP_TIMEOUT_MS || 60_000);\\nconst ACTIVITY_ACTION_DECIDE_CAPABILITY = \\"activity-action-decide-v1\\";\\nconst ACTIVITY_ACTION_RESOLVE_CAPABILITY = \\"activity-action-resolve-v1\\";\\nconst AUDIT_READ_ONLY_CAPABILITY = \\"audit-read-only-v1\\";\\nconst CONTENT_LENGTH_RE = /Content-Length:\\\\s*(\\\\d+)/i;\\nconst PLUGIN_ROOT = path.resolve(__dirname, \\"..\\");\\n\\nconst tools = [\\n {\\n name: \\"fclt_setup\\",\\n description:\\n \\"Bootstrap or repair the global fclt loop and optionally return an exact no-write project enrollment plan.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n scope: {\\n type: \\"string\\",\\n enum: [\\"global\\", \\"global_and_project\\"],\\n },\\n cwd: { type: \\"string\\" },\\n dryRun: { type: \\"boolean\\" },\\n installCodexPlugin: { type: \\"boolean\\" },\\n approve: { type: \\"boolean\\" },\\n },\\n required: [\\"scope\\"],\\n },\\n },\\n {\\n name: \\"fclt_runtime\\",\\n description:\\n \\"Discover, bootstrap, update, or roll back the verified fclt runtime used by this plugin.\\",\\n inputSchema: {\\n type: \\"object\\",\\n additionalProperties: false,\\n properties: {\\n action: {\\n type: \\"string\\",\\n enum: [\\"status\\", \\"check\\", \\"policy\\", \\"stage\\", \\"apply\\", \\"rollback\\"],\\n },\\n version: { type: \\"string\\" },\\n expectedSha256: { type: \\"string\\", pattern: \\"^[a-f0-9]{64}$\\" },\\n expectedActiveVersion: { type: \\"string\\" },\\n pinnedVersion: { type: \\"string\\" },\\n clearPin: { type: \\"boolean\\" },\\n updateChecksEnabled: { type: \\"boolean\\" },\\n approve: { type: \\"boolean\\" },\\n },\\n },\\n },\\n {\\n name: \\"fclt_capability\\",\\n description:\\n \\"Inspect fclt capability, provenance, templates, snippets, adapters, and managed status without exposing secrets.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n action: {\\n type: \\"string\\",\\n enum: [\\n \\"scan\\",\\n \\"inventory\\",\\n \\"list\\",\\n \\"show\\",\\n \\"find\\",\\n \\"graph\\",\\n \\"adapters\\",\\n \\"managed_status\\",\\n \\"templates_list\\",\\n \\"snippet_list\\",\\n \\"snippet_show\\",\\n ],\\n },\\n scope: { type: \\"string\\", enum: [\\"auto\\", \\"global\\", \\"project\\"] },\\n cwd: { type: \\"string\\" },\\n kind: {\\n type: \\"string\\",\\n enum: [\\n \\"skills\\",\\n \\"mcp\\",\\n \\"agents\\",\\n \\"automations\\",\\n \\"snippets\\",\\n \\"instructions\\",\\n ],\\n },\\n query: { type: \\"string\\" },\\n selector: { type: \\"string\\" },\\n graphMode: { type: \\"string\\", enum: [\\"show\\", \\"deps\\", \\"dependents\\"] },\\n },\\n required: [\\"action\\"],\\n },\\n },\\n {\\n name: \\"fclt_workflow\\",\\n description:\\n \\"Run typed writeback and evolution review operations. Canonical apply and cross-scope promotion are deliberately withheld.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n action: {\\n type: \\"string\\",\\n enum: [\\n \\"writeback_list\\",\\n \\"writeback_show\\",\\n \\"writeback_group\\",\\n \\"writeback_summarize\\",\\n \\"writeback_add\\",\\n \\"writeback_link\\",\\n \\"writeback_disposition\\",\\n \\"evolve_assess\\",\\n \\"evolve_list\\",\\n \\"evolve_show\\",\\n \\"evolve_propose\\",\\n \\"evolve_draft\\",\\n \\"evolve_review\\",\\n \\"evolve_verify\\",\\n ],\\n },\\n scope: { type: \\"string\\", enum: [\\"global\\", \\"project\\"] },\\n cwd: { type: \\"string\\" },\\n id: { type: \\"string\\" },\\n kind: { type: \\"string\\" },\\n category: {\\n type: \\"string\\",\\n enum: [\\"friction\\", \\"opportunity\\", \\"reusable-success\\"],\\n },\\n summary: { type: \\"string\\" },\\n details: { type: \\"string\\", maxLength: 2000 },\\n impact: { type: \\"string\\", maxLength: 1000 },\\n attemptedWorkaround: { type: \\"string\\", maxLength: 1000 },\\n desiredOutcome: { type: \\"string\\", maxLength: 1000 },\\n sensitivity: {\\n type: \\"string\\",\\n enum: [\\"public\\", \\"internal\\", \\"private\\"],\\n },\\n asset: { type: \\"string\\" },\\n evidence: { type: \\"array\\", items: { type: \\"string\\" } },\\n confidence: { type: \\"string\\", enum: [\\"low\\", \\"medium\\", \\"high\\"] },\\n by: { type: \\"string\\", enum: [\\"asset\\", \\"kind\\", \\"domain\\"] },\\n issue: { type: \\"string\\" },\\n disposition: {\\n type: \\"string\\",\\n enum: [\\"propose\\", \\"apply-local\\", \\"task\\", \\"resolve-watch\\", \\"defer\\"],\\n },\\n target: { type: \\"string\\" },\\n nextTrigger: { type: \\"string\\" },\\n expectedOutcome: { type: \\"string\\" },\\n append: { type: \\"string\\" },\\n reason: { type: \\"string\\" },\\n byProposal: { type: \\"string\\" },\\n effectiveness: {\\n type: \\"string\\",\\n enum: [\\"improved\\", \\"unchanged\\", \\"regressed\\", \\"inconclusive\\"],\\n },\\n note: { type: \\"string\\" },\\n approve: { type: \\"boolean\\" },\\n },\\n required: [\\"action\\"],\\n },\\n },\\n {\\n name: \\"fclt_sync\\",\\n description:\\n \\"Inspect managed state or preview a scoped tool sync. Apply and live adoption remain withheld pending transaction-safe APIs.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n action: { type: \\"string\\", enum: [\\"status\\", \\"preview\\"] },\\n scope: { type: \\"string\\", enum: [\\"global\\", \\"project\\"] },\\n cwd: { type: \\"string\\" },\\n tool: { type: \\"string\\" },\\n },\\n required: [\\"action\\", \\"scope\\"],\\n },\\n },\\n {\\n name: \\"fclt_registry\\",\\n description:\\n \\"Search and verify remote capability, run typed source reconciliation reviews, resolve one opaque activity action locator, or record one revision-bound signal decision. Canonical and external mutation remains withheld.\\",\\n inputSchema: {\\n oneOf: [\\n {\\n type: \\"object\\",\\n properties: {\\n action: { const: \\"activity_decide\\" },\\n locator: {\\n type: \\"string\\",\\n pattern: \\"^fclt-act-v[0-9]+\\\\\\\\.[a-f0-9]{64}\\\\\\\\.[a-f0-9]{64}$\\",\\n },\\n decision: {\\n type: \\"string\\",\\n enum: [\\"accept\\", \\"redirect\\", \\"reject\\", \\"defer\\"],\\n },\\n expectedRevision: { type: \\"integer\\", minimum: 1 },\\n actor: {\\n type: \\"string\\",\\n pattern: \\"^[A-Za-z0-9][A-Za-z0-9._:@-]{0,127}$\\",\\n },\\n approvalReference: { type: \\"string\\", minLength: 1, maxLength: 500 },\\n note: { type: \\"string\\", minLength: 1, maxLength: 1000 },\\n redirectTarget: { type: \\"string\\", minLength: 1, maxLength: 500 },\\n approve: { const: true },\\n },\\n required: [\\n \\"action\\",\\n \\"locator\\",\\n \\"decision\\",\\n \\"expectedRevision\\",\\n \\"actor\\",\\n \\"approve\\",\\n ],\\n allOf: [\\n {\\n oneOf: [\\n {\\n required: [\\"approvalReference\\"],\\n not: { required: [\\"note\\"] },\\n },\\n {\\n required: [\\"note\\"],\\n not: { required: [\\"approvalReference\\"] },\\n },\\n ],\\n },\\n ],\\n additionalProperties: false,\\n },\\n {\\n type: \\"object\\",\\n properties: {\\n action: { const: \\"activity_resolve\\" },\\n locator: {\\n type: \\"string\\",\\n pattern: \\"^fclt-act-v[0-9]+\\\\\\\\.[a-f0-9]{64}\\\\\\\\.[a-f0-9]{64}$\\",\\n },\\n },\\n required: [\\"action\\", \\"locator\\"],\\n additionalProperties: false,\\n },\\n {\\n type: \\"object\\",\\n properties: {\\n action: {\\n type: \\"string\\",\\n enum: [\\n \\"search\\",\\n \\"verify_source\\",\\n \\"source_list\\",\\n \\"install_preview\\",\\n \\"update_check\\",\\n \\"reconcile_status\\",\\n \\"reconcile\\",\\n ],\\n },\\n scope: { type: \\"string\\", enum: [\\"global\\", \\"project\\"] },\\n cwd: { type: \\"string\\" },\\n query: { type: \\"string\\" },\\n source: { type: \\"string\\" },\\n item: { type: \\"string\\" },\\n as: { type: \\"string\\" },\\n since: {\\n type: \\"string\\",\\n pattern:\\n \\"^\\\\\\\\d{4}-\\\\\\\\d{2}-\\\\\\\\d{2}(?:T\\\\\\\\d{2}:\\\\\\\\d{2}:\\\\\\\\d{2}(?:\\\\\\\\.\\\\\\\\d+)?(?:Z|[+-]\\\\\\\\d{2}:\\\\\\\\d{2}))?$\\",\\n },\\n until: {\\n type: \\"string\\",\\n pattern:\\n \\"^\\\\\\\\d{4}-\\\\\\\\d{2}-\\\\\\\\d{2}(?:T\\\\\\\\d{2}:\\\\\\\\d{2}:\\\\\\\\d{2}(?:\\\\\\\\.\\\\\\\\d+)?(?:Z|[+-]\\\\\\\\d{2}:\\\\\\\\d{2}))?$\\",\\n },\\n sourceIds: {\\n type: \\"array\\",\\n items: {\\n type: \\"string\\",\\n pattern: \\"^[A-Za-z0-9][A-Za-z0-9._-]*$\\",\\n },\\n },\\n incremental: { type: \\"boolean\\" },\\n },\\n required: [\\"action\\"],\\n additionalProperties: false,\\n },\\n ],\\n },\\n },\\n {\\n name: \\"fclt_audit\\",\\n description:\\n \\"Run a structured, redacted, non-interactive fclt security audit with no report or index writes.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n action: { type: \\"string\\", enum: [\\"scan\\"] },\\n cwd: { type: \\"string\\" },\\n target: { type: \\"string\\" },\\n severity: {\\n type: \\"string\\",\\n enum: [\\"critical\\", \\"high\\", \\"medium\\", \\"low\\"],\\n },\\n },\\n required: [\\"action\\"],\\n },\\n },\\n {\\n name: \\"fclt_automation\\",\\n description:\\n \\"Read one aggregate activity set across all configured loops by default, or inspect one explicit loop scope. Schedule and canonical mutation remain CLI-only.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n action: {\\n type: \\"string\\",\\n enum: [\\n \\"autosync_status\\",\\n \\"loop_status\\",\\n \\"loop_activity\\",\\n \\"loop_preview\\",\\n ],\\n },\\n scope: {\\n type: \\"string\\",\\n enum: [\\"all\\", \\"global\\", \\"project\\"],\\n },\\n cwd: { type: \\"string\\" },\\n tool: { type: \\"string\\" },\\n },\\n required: [\\"action\\"],\\n oneOf: [\\n {\\n properties: {\\n action: { const: \\"loop_activity\\" },\\n scope: {\\n type: \\"string\\",\\n enum: [\\"all\\", \\"global\\", \\"project\\"],\\n default: \\"all\\",\\n },\\n },\\n required: [\\"action\\"],\\n },\\n {\\n properties: {\\n action: {\\n enum: [\\"autosync_status\\", \\"loop_status\\", \\"loop_preview\\"],\\n },\\n scope: { type: \\"string\\", enum: [\\"global\\", \\"project\\"] },\\n },\\n required: [\\"action\\", \\"scope\\"],\\n },\\n ],\\n },\\n },\\n {\\n name: \\"fclt_status\\",\\n description:\\n \\"Return fclt status for the current, global, or project scope.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n scope: { type: \\"string\\", enum: [\\"auto\\", \\"global\\", \\"project\\"] },\\n cwd: { type: \\"string\\" },\\n },\\n },\\n },\\n {\\n name: \\"fclt_doctor\\",\\n description: \\"Run read-only fclt doctor checks and return JSON output.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n scope: { type: \\"string\\", enum: [\\"auto\\", \\"global\\", \\"project\\"] },\\n cwd: { type: \\"string\\" },\\n },\\n },\\n },\\n {\\n name: \\"fclt_paths\\",\\n description: \\"Return canonical, generated, review, and runtime fclt paths.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n scope: { type: \\"string\\", enum: [\\"auto\\", \\"global\\", \\"project\\"] },\\n cwd: { type: \\"string\\" },\\n },\\n },\\n },\\n {\\n name: \\"fclt_init_operating_model\\",\\n description: \\"Install or update the built-in operating-model pack.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n scope: { type: \\"string\\", enum: [\\"global\\", \\"project\\"] },\\n cwd: { type: \\"string\\" },\\n update: { type: \\"boolean\\" },\\n dryRun: { type: \\"boolean\\" },\\n force: { type: \\"boolean\\" },\\n approve: { type: \\"boolean\\" },\\n },\\n required: [\\"scope\\"],\\n },\\n },\\n {\\n name: \\"fclt_writeback_add\\",\\n description: \\"Record a durable fclt writeback with evidence.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n scope: { type: \\"string\\", enum: [\\"global\\", \\"project\\"] },\\n cwd: { type: \\"string\\" },\\n kind: { type: \\"string\\" },\\n category: {\\n type: \\"string\\",\\n enum: [\\"friction\\", \\"opportunity\\", \\"reusable-success\\"],\\n },\\n summary: { type: \\"string\\" },\\n details: { type: \\"string\\", maxLength: 2000 },\\n impact: { type: \\"string\\", maxLength: 1000 },\\n attemptedWorkaround: { type: \\"string\\", maxLength: 1000 },\\n desiredOutcome: { type: \\"string\\", maxLength: 1000 },\\n sensitivity: {\\n type: \\"string\\",\\n enum: [\\"public\\", \\"internal\\", \\"private\\"],\\n },\\n asset: { type: \\"string\\" },\\n evidence: { type: \\"string\\" },\\n confidence: {\\n type: \\"string\\",\\n enum: [\\"low\\", \\"medium\\", \\"high\\"],\\n },\\n approve: { type: \\"boolean\\" },\\n },\\n required: [\\"scope\\", \\"kind\\", \\"summary\\", \\"evidence\\", \\"approve\\"],\\n },\\n },\\n {\\n name: \\"fclt_writeback_review\\",\\n description: \\"List, group, or summarize current fclt writebacks.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n scope: { type: \\"string\\", enum: [\\"global\\", \\"project\\"] },\\n cwd: { type: \\"string\\" },\\n mode: { type: \\"string\\", enum: [\\"list\\", \\"group\\", \\"summarize\\"] },\\n by: { type: \\"string\\" },\\n },\\n },\\n },\\n {\\n name: \\"fclt_evolve\\",\\n description:\\n \\"Assess, list, propose, draft, or review fclt evolution proposals.\\",\\n inputSchema: {\\n type: \\"object\\",\\n properties: {\\n scope: { type: \\"string\\", enum: [\\"auto\\", \\"global\\", \\"project\\"] },\\n cwd: { type: \\"string\\" },\\n action: {\\n type: \\"string\\",\\n enum: [\\"assess\\", \\"list\\", \\"propose\\", \\"draft\\", \\"review\\", \\"show\\"],\\n },\\n id: { type: \\"string\\" },\\n asset: { type: \\"string\\" },\\n approve: { type: \\"boolean\\" },\\n },\\n },\\n },\\n];\\n\\nfor (const tool of tools) {\\n if (!tool.inputSchema.oneOf || tool.inputSchema.properties) {\\n tool.inputSchema.additionalProperties = false;\\n }\\n}\\n\\nfunction isPlainObject(value) {\\n return Boolean(value) && typeof value === \\"object\\" && !Array.isArray(value);\\n}\\n\\nfunction validateToolArguments(name, args) {\\n if (!isPlainObject(args)) {\\n throw new Error(`${name} arguments must be an object`);\\n }\\n const tool = tools.find((entry) => entry.name === name);\\n if (!tool) {\\n throw new Error(`Unknown tool: ${name}`);\\n }\\n const rootSchema = tool.inputSchema;\\n const schema =\\n rootSchema.oneOf && !rootSchema.properties\\n ? rootSchema.oneOf.find((branch) => {\\n const action = branch.properties?.action;\\n return (\\n action?.const === args.action || action?.enum?.includes(args.action)\\n );\\n })\\n : rootSchema;\\n if (!schema) {\\n throw new Error(`${name}.action is not an allowed value`);\\n }\\n const properties = schema.properties || {};\\n const unknown = Object.keys(args).filter((key) => !(key in properties));\\n if (unknown.length > 0) {\\n throw new Error(\\n `${name} received unknown argument fields: ${unknown.join(\\", \\")}`\\n );\\n }\\n for (const required of schema.required || []) {\\n if (!(required in args)) {\\n throw new Error(`${name} requires ${required}`);\\n }\\n }\\n for (const constraint of schema.allOf || []) {\\n if (Array.isArray(constraint.oneOf)) {\\n const matches = constraint.oneOf.filter((branch) => {\\n const hasRequired = (branch.required || []).every((key) => key in args);\\n const violatesNot = branch.not?.required\\n ? branch.not.required.every((key) => key in args)\\n : false;\\n return hasRequired && !violatesNot;\\n });\\n if (matches.length !== 1) {\\n throw new Error(\\n `${name} arguments do not match exactly one allowed shape`\\n );\\n }\\n }\\n }\\n for (const [key, value] of Object.entries(args)) {\\n const property = properties[key];\\n if (property.const !== undefined && value !== property.const) {\\n throw new Error(`${name}.${key} is not an allowed value`);\\n }\\n if (!property.type) {\\n continue;\\n }\\n const validType =\\n property.type === \\"array\\"\\n ? Array.isArray(value)\\n : property.type === \\"object\\"\\n ? isPlainObject(value)\\n : property.type === \\"integer\\"\\n ? Number.isSafeInteger(value)\\n : typeof value === property.type;\\n if (!validType) {\\n throw new Error(`${name}.${key} must be ${property.type}`);\\n }\\n if (property.enum && !property.enum.includes(value)) {\\n throw new Error(`${name}.${key} is not an allowed value`);\\n }\\n if (\\n typeof value === \\"string\\" &&\\n ((property.minLength !== undefined &&\\n value.length < property.minLength) ||\\n (property.maxLength !== undefined && value.length > property.maxLength))\\n ) {\\n throw new Error(`${name}.${key} has an invalid length`);\\n }\\n if (\\n typeof value === \\"number\\" &&\\n property.minimum !== undefined &&\\n value < property.minimum\\n ) {\\n throw new Error(`${name}.${key} is below the minimum`);\\n }\\n if (property.pattern && !new RegExp(property.pattern).test(value)) {\\n throw new Error(`${name}.${key} has an invalid format`);\\n }\\n if (\\n property.type === \\"array\\" &&\\n property.items?.type &&\\n value.some((item) => typeof item !== property.items.type)\\n ) {\\n throw new Error(`${name}.${key} contains an invalid item`);\\n }\\n if (\\n property.type === \\"array\\" &&\\n property.items?.pattern &&\\n value.some((item) => !new RegExp(property.items.pattern).test(item))\\n ) {\\n throw new Error(`${name}.${key} contains an invalid item`);\\n }\\n }\\n}\\n\\nfunction scopeArgs(scope) {\\n if (scope === \\"global\\") {\\n return [\\"--global\\"];\\n }\\n if (scope === \\"project\\") {\\n return [\\"--project\\"];\\n }\\n return [];\\n}\\n\\nfunction boolFlag(name, value) {\\n return value ? [name] : [];\\n}\\n\\nfunction stringFlag(name, value) {\\n return typeof value === \\"string\\" && value.trim() ? [name, value] : [];\\n}\\n\\nfunction repeatedStringFlag(name, values) {\\n return Array.isArray(values)\\n ? values.flatMap((value) => stringFlag(name, value))\\n : [];\\n}\\n\\nfunction requireString(name, value) {\\n if (typeof value !== \\"string\\" || !value.trim()) {\\n throw new Error(`${name} is required`);\\n }\\n return value;\\n}\\n\\nfunction requireMutationApproval(name, args) {\\n if (args.scope !== \\"global\\" && args.scope !== \\"project\\") {\\n throw new Error(`${name} requires an explicit global or project scope`);\\n }\\n if (args.approve !== true) {\\n throw new Error(`${name} requires approve=true`);\\n }\\n}\\n\\nfunction capabilityCommand(args) {\\n const action = args.action;\\n if (action === \\"scan\\") {\\n return [\\"scan\\", \\"--json\\"];\\n }\\n if (action === \\"inventory\\") {\\n return [\\"inventory\\", ...scopeArgs(args.scope), \\"--json\\"];\\n }\\n if (action === \\"list\\") {\\n return [\\"list\\", args.kind || \\"skills\\", ...scopeArgs(args.scope), \\"--json\\"];\\n }\\n if (action === \\"show\\") {\\n return [\\n \\"show\\",\\n requireString(\\"selector\\", args.selector),\\n ...scopeArgs(args.scope),\\n ];\\n }\\n if (action === \\"find\\") {\\n return [\\n \\"find\\",\\n requireString(\\"query\\", args.query),\\n ...scopeArgs(args.scope),\\n \\"--json\\",\\n ];\\n }\\n if (action === \\"graph\\") {\\n return [\\n \\"graph\\",\\n args.graphMode || \\"show\\",\\n requireString(\\"selector\\", args.selector),\\n ...scopeArgs(args.scope),\\n \\"--json\\",\\n ];\\n }\\n if (action === \\"adapters\\") {\\n return [\\"adapters\\", \\"--json\\"];\\n }\\n if (action === \\"managed_status\\") {\\n return [\\"managed\\", ...scopeArgs(args.scope)];\\n }\\n if (action === \\"templates_list\\") {\\n return [\\"templates\\", \\"list\\", \\"--json\\"];\\n }\\n if (action === \\"snippet_list\\") {\\n return [\\"snippets\\", \\"list\\", \\"--json\\"];\\n }\\n if (action === \\"snippet_show\\") {\\n return [\\n \\"snippets\\",\\n \\"show\\",\\n requireString(\\"selector\\", args.selector),\\n \\"--json\\",\\n ];\\n }\\n throw new Error(`Unsupported capability action: ${action}`);\\n}\\n\\nconst WORKFLOW_MUTATIONS = new Set([\\n \\"writeback_add\\",\\n \\"writeback_link\\",\\n \\"writeback_disposition\\",\\n \\"writeback_dismiss\\",\\n \\"writeback_promote\\",\\n \\"evolve_propose\\",\\n \\"evolve_draft\\",\\n \\"evolve_review\\",\\n \\"evolve_accept\\",\\n \\"evolve_reject\\",\\n \\"evolve_supersede\\",\\n \\"evolve_verify\\",\\n]);\\n\\nconst WORKFLOW_ACTION_FIELDS = {\\n writeback_list: [],\\n writeback_show: [\\"id\\"],\\n writeback_group: [\\"by\\"],\\n writeback_summarize: [\\"by\\"],\\n writeback_add: [\\n \\"kind\\",\\n \\"category\\",\\n \\"summary\\",\\n \\"details\\",\\n \\"impact\\",\\n \\"attemptedWorkaround\\",\\n \\"desiredOutcome\\",\\n \\"sensitivity\\",\\n \\"asset\\",\\n \\"evidence\\",\\n \\"confidence\\",\\n \\"approve\\",\\n ],\\n writeback_link: [\\"id\\", \\"issue\\", \\"approve\\"],\\n writeback_disposition: [\\n \\"id\\",\\n \\"disposition\\",\\n \\"target\\",\\n \\"nextTrigger\\",\\n \\"expectedOutcome\\",\\n \\"approve\\",\\n ],\\n evolve_assess: [\\"asset\\"],\\n evolve_list: [],\\n evolve_show: [\\"id\\"],\\n evolve_propose: [\\"asset\\", \\"approve\\"],\\n evolve_draft: [\\"id\\", \\"append\\", \\"approve\\"],\\n evolve_review: [\\"id\\", \\"approve\\"],\\n evolve_verify: [\\"id\\", \\"effectiveness\\", \\"evidence\\", \\"note\\", \\"approve\\"],\\n};\\n\\nfunction requireOnlyWorkflowFields(args) {\\n const fields = WORKFLOW_ACTION_FIELDS[args.action];\\n if (!fields) {\\n return;\\n }\\n const allowed = new Set([\\"action\\", \\"scope\\", \\"cwd\\", ...fields]);\\n const unexpected = Object.keys(args).filter((key) => !allowed.has(key));\\n if (unexpected.length > 0) {\\n throw new Error(\\n `${args.action} received unsupported fields: ${unexpected.join(\\", \\")}`\\n );\\n }\\n}\\n\\nfunction workflowCommand(args) {\\n const action = args.action;\\n requireOnlyWorkflowFields(args);\\n if (WORKFLOW_MUTATIONS.has(action)) {\\n requireMutationApproval(action, args);\\n }\\n const scope = scopeArgs(args.scope);\\n if (action === \\"writeback_list\\") {\\n return [\\"ai\\", \\"writeback\\", ...scope, \\"list\\", \\"--json\\"];\\n }\\n if (action === \\"writeback_show\\") {\\n return [\\n \\"ai\\",\\n \\"writeback\\",\\n ...scope,\\n \\"show\\",\\n requireString(\\"id\\", args.id),\\n \\"--json\\",\\n ];\\n }\\n if (action === \\"writeback_group\\" || action === \\"writeback_summarize\\") {\\n return [\\n \\"ai\\",\\n \\"writeback\\",\\n ...scope,\\n action === \\"writeback_group\\" ? \\"group\\" : \\"summarize\\",\\n ...stringFlag(\\"--by\\", args.by),\\n \\"--json\\",\\n ];\\n }\\n if (action === \\"writeback_add\\") {\\n if (!Array.isArray(args.evidence) || args.evidence.length === 0) {\\n throw new Error(\\"writeback_add requires at least one evidence reference\\");\\n }\\n return [\\n \\"ai\\",\\n \\"writeback\\",\\n ...scope,\\n \\"add\\",\\n \\"--kind\\",\\n requireString(\\"kind\\", args.kind),\\n \\"--summary\\",\\n requireString(\\"summary\\", args.summary),\\n ...stringFlag(\\"--asset\\", args.asset),\\n ...stringFlag(\\"--category\\", args.category),\\n ...stringFlag(\\"--details\\", args.details),\\n ...stringFlag(\\"--impact\\", args.impact),\\n ...stringFlag(\\"--attempted-workaround\\", args.attemptedWorkaround),\\n ...stringFlag(\\"--desired-outcome\\", args.desiredOutcome),\\n ...stringFlag(\\"--sensitivity\\", args.sensitivity),\\n ...repeatedStringFlag(\\"--evidence\\", args.evidence),\\n ...stringFlag(\\"--confidence\\", args.confidence),\\n \\"--json\\",\\n ];\\n }\\n if (action === \\"writeback_link\\") {\\n return [\\n \\"ai\\",\\n \\"writeback\\",\\n ...scope,\\n \\"link\\",\\n requireString(\\"id\\", args.id),\\n \\"--issue\\",\\n requireString(\\"issue\\", args.issue),\\n ];\\n }\\n if (action === \\"writeback_disposition\\") {\\n return [\\n \\"ai\\",\\n \\"writeback\\",\\n ...scope,\\n \\"disposition\\",\\n requireString(\\"id\\", args.id),\\n \\"--type\\",\\n requireString(\\"disposition\\", args.disposition),\\n ...stringFlag(\\"--target\\", args.target),\\n ...stringFlag(\\"--next-trigger\\", args.nextTrigger),\\n ...stringFlag(\\"--expected-outcome\\", args.expectedOutcome),\\n ];\\n }\\n if (action === \\"writeback_dismiss\\" || action === \\"writeback_promote\\") {\\n return [\\n \\"ai\\",\\n \\"writeback\\",\\n ...scope,\\n action === \\"writeback_dismiss\\" ? \\"dismiss\\" : \\"promote\\",\\n requireString(\\"id\\", args.id),\\n ];\\n }\\n if (action === \\"evolve_assess\\") {\\n return [\\n \\"ai\\",\\n \\"evolve\\",\\n ...scope,\\n \\"assess\\",\\n ...stringFlag(\\"--asset\\", args.asset),\\n \\"--json\\",\\n ];\\n }\\n if (action === \\"evolve_list\\") {\\n return [\\"ai\\", \\"evolve\\", ...scope, \\"list\\", \\"--json\\"];\\n }\\n if (action === \\"evolve_show\\") {\\n return [\\n \\"ai\\",\\n \\"evolve\\",\\n ...scope,\\n \\"show\\",\\n requireString(\\"id\\", args.id),\\n \\"--json\\",\\n ];\\n }\\n if (action === \\"evolve_propose\\") {\\n return [\\n \\"ai\\",\\n \\"evolve\\",\\n ...scope,\\n \\"propose\\",\\n \\"--asset\\",\\n requireString(\\"asset\\", args.asset),\\n \\"--json\\",\\n ];\\n }\\n if (action === \\"evolve_draft\\") {\\n return [\\n \\"ai\\",\\n \\"evolve\\",\\n ...scope,\\n \\"draft\\",\\n requireString(\\"id\\", args.id),\\n ...stringFlag(\\"--append\\", args.append),\\n ];\\n }\\n if (action === \\"evolve_review\\" || action === \\"evolve_accept\\") {\\n return [\\n \\"ai\\",\\n \\"evolve\\",\\n ...scope,\\n action === \\"evolve_review\\" ? \\"review\\" : \\"accept\\",\\n requireString(\\"id\\", args.id),\\n ];\\n }\\n if (action === \\"evolve_reject\\") {\\n return [\\n \\"ai\\",\\n \\"evolve\\",\\n ...scope,\\n \\"reject\\",\\n requireString(\\"id\\", args.id),\\n \\"--reason\\",\\n requireString(\\"reason\\", args.reason),\\n ];\\n }\\n if (action === \\"evolve_supersede\\") {\\n return [\\n \\"ai\\",\\n \\"evolve\\",\\n ...scope,\\n \\"supersede\\",\\n requireString(\\"id\\", args.id),\\n \\"--by\\",\\n requireString(\\"byProposal\\", args.byProposal),\\n ];\\n }\\n if (action === \\"evolve_verify\\") {\\n if (!Array.isArray(args.evidence) || args.evidence.length === 0) {\\n throw new Error(\\"evolve_verify requires at least one evidence reference\\");\\n }\\n return [\\n \\"ai\\",\\n \\"evolve\\",\\n ...scope,\\n \\"verify\\",\\n requireString(\\"id\\", args.id),\\n \\"--effectiveness\\",\\n requireString(\\"effectiveness\\", args.effectiveness),\\n ...repeatedStringFlag(\\"--evidence\\", args.evidence),\\n ...stringFlag(\\"--note\\", args.note),\\n ];\\n }\\n throw new Error(`Unsupported workflow action: ${action}`);\\n}\\n\\nfunction syncCommand(args) {\\n if (args.action === \\"status\\") {\\n return [\\"managed\\", ...scopeArgs(args.scope)];\\n }\\n if (args.action === \\"preview\\") {\\n return [\\n \\"sync\\",\\n ...(args.tool ? [args.tool] : []),\\n \\"--dry-run\\",\\n ...scopeArgs(args.scope),\\n ];\\n }\\n throw new Error(`Unsupported sync action: ${args.action}`);\\n}\\n\\nfunction requireOnlyRegistryFields(args, fields) {\\n const allowed = new Set([\\"action\\", \\"scope\\", \\"cwd\\", ...fields]);\\n const unexpected = Object.keys(args).filter((key) => !allowed.has(key));\\n if (unexpected.length > 0) {\\n throw new Error(\\n `${args.action} received unsupported fields: ${unexpected.join(\\", \\")}`\\n );\\n }\\n}\\n\\nfunction registryCommand(args) {\\n if (args.action === \\"activity_decide\\") {\\n const expectedFields = new Set([\\n \\"action\\",\\n \\"locator\\",\\n \\"decision\\",\\n \\"expectedRevision\\",\\n \\"actor\\",\\n \\"approvalReference\\",\\n \\"note\\",\\n \\"redirectTarget\\",\\n \\"approve\\",\\n ]);\\n const unexpected = Object.keys(args).filter(\\n (key) => !expectedFields.has(key)\\n );\\n if (unexpected.length > 0) {\\n throw new Error(\\n `activity_decide received unsupported fields: ${unexpected.join(\\", \\")}`\\n );\\n }\\n if (args.approve !== true) {\\n throw new Error(\\"activity_decide requires approve=true\\");\\n }\\n if (!Number.isSafeInteger(args.expectedRevision)) {\\n throw new Error(\\n \\"activity_decide requires expectedRevision as an integer\\"\\n );\\n }\\n return [\\n \\"ai\\",\\n \\"loop\\",\\n \\"decide\\",\\n requireString(\\"locator\\", args.locator),\\n \\"--decision\\",\\n requireString(\\"decision\\", args.decision),\\n \\"--expected-revision\\",\\n String(args.expectedRevision),\\n \\"--actor\\",\\n requireString(\\"actor\\", args.actor),\\n ...stringFlag(\\"--approval-ref\\", args.approvalReference),\\n ...stringFlag(\\"--note\\", args.note),\\n ...stringFlag(\\"--redirect-target\\", args.redirectTarget),\\n \\"--approve\\",\\n \\"--json\\",\\n ];\\n }\\n if (args.action === \\"activity_resolve\\") {\\n const unexpected = Object.keys(args).filter(\\n (key) => key !== \\"action\\" && key !== \\"locator\\"\\n );\\n if (unexpected.length > 0) {\\n throw new Error(\\n `activity_resolve received unsupported fields: ${unexpected.join(\\", \\")}`\\n );\\n }\\n return [\\n \\"ai\\",\\n \\"loop\\",\\n \\"resolve\\",\\n requireString(\\"locator\\", args.locator),\\n \\"--json\\",\\n ];\\n }\\n if (\\n args.scope === \\"project\\" &&\\n args.action !== \\"reconcile_status\\" &&\\n args.action !== \\"reconcile\\"\\n ) {\\n throw new Error(`${args.action} only supports global scope`);\\n }\\n if (args.action === \\"search\\") {\\n requireOnlyRegistryFields(args, [\\"query\\"]);\\n return [\\"search\\", requireString(\\"query\\", args.query), \\"--json\\"];\\n }\\n if (args.action === \\"verify_source\\") {\\n requireOnlyRegistryFields(args, [\\"source\\"]);\\n return [\\"verify-source\\", requireString(\\"source\\", args.source), \\"--json\\"];\\n }\\n if (args.action === \\"source_list\\") {\\n requireOnlyRegistryFields(args, []);\\n return [\\"sources\\", \\"list\\", \\"--json\\"];\\n }\\n if (args.action === \\"install_preview\\") {\\n requireOnlyRegistryFields(args, [\\"item\\", \\"as\\"]);\\n return [\\n \\"install\\",\\n requireString(\\"item\\", args.item),\\n ...stringFlag(\\"--as\\", args.as),\\n \\"--dry-run\\",\\n \\"--strict-source-trust\\",\\n \\"--json\\",\\n ];\\n }\\n if (args.action === \\"update_check\\") {\\n requireOnlyRegistryFields(args, []);\\n return [\\"update\\", \\"--strict-source-trust\\", \\"--json\\"];\\n }\\n if (args.action === \\"reconcile_status\\") {\\n requireOnlyRegistryFields(args, []);\\n if (args.scope !== \\"global\\" && args.scope !== \\"project\\") {\\n throw new Error(\\n \\"reconcile_status requires an explicit global or project scope\\"\\n );\\n }\\n return [\\"ai\\", \\"review\\", ...scopeArgs(args.scope), \\"status\\", \\"--json\\"];\\n }\\n if (args.action === \\"reconcile\\") {\\n requireOnlyRegistryFields(args, [\\n \\"since\\",\\n \\"until\\",\\n \\"sourceIds\\",\\n \\"incremental\\",\\n ]);\\n if (args.scope !== \\"global\\" && args.scope !== \\"project\\") {\\n throw new Error(\\"reconcile requires an explicit global or project scope\\");\\n }\\n return [\\n \\"ai\\",\\n \\"review\\",\\n ...scopeArgs(args.scope),\\n \\"reconcile\\",\\n \\"--since\\",\\n requireString(\\"since\\", args.since),\\n ...stringFlag(\\"--until\\", args.until),\\n ...repeatedStringFlag(\\"--source\\", args.sourceIds),\\n ...boolFlag(\\"--incremental\\", args.incremental),\\n \\"--json\\",\\n ];\\n }\\n throw new Error(`Unsupported registry action: ${args.action}`);\\n}\\n\\nfunction isSubpath(child, parent) {\\n const relative = path.relative(parent, child);\\n return (\\n relative === \\"\\" || !(relative.startsWith(\\"..\\") || path.isAbsolute(relative))\\n );\\n}\\n\\nfunction isDirectory(candidate) {\\n try {\\n return fs.statSync(candidate).isDirectory();\\n } catch {\\n return false;\\n }\\n}\\n\\nfunction resolveWorkspaceCwd({ allowHomeFallback = true } = {}) {\\n const candidates = [\\n process.env.FCLT_MCP_WORKSPACE_CWD,\\n process.env.INIT_CWD,\\n process.env.PWD,\\n ];\\n for (const candidate of candidates) {\\n if (typeof candidate !== \\"string\\" || !candidate.trim()) {\\n continue;\\n }\\n const resolved = path.resolve(candidate);\\n const isHome = resolved === path.resolve(os.homedir());\\n if (\\n (allowHomeFallback || !isHome) &&\\n !isSubpath(resolved, PLUGIN_ROOT) &&\\n isDirectory(resolved)\\n ) {\\n return resolved;\\n }\\n }\\n if (allowHomeFallback && isDirectory(os.homedir())) {\\n return os.homedir();\\n }\\n return undefined;\\n}\\n\\nfunction resolveToolCwd(name, args = {}) {\\n if (typeof args.cwd === \\"string\\" && args.cwd.trim()) {\\n return args.cwd;\\n }\\n const inferred = resolveWorkspaceCwd({\\n allowHomeFallback: args.scope !== \\"project\\",\\n });\\n if (inferred) {\\n return inferred;\\n }\\n if (args.scope === \\"project\\") {\\n throw new Error(\\n `${name} with project scope requires a cwd for the target workspace`\\n );\\n }\\n return process.cwd();\\n}\\n\\nfunction commandForTool(name, args = {}) {\\n switch (name) {\\n case \\"fclt_setup\\": {\\n const apply = args.dryRun === false;\\n if (apply && args.approve !== true) {\\n throw new Error(\\"fclt_setup apply requires approve=true\\");\\n }\\n if (\\n args.scope === \\"global_and_project\\" &&\\n (typeof args.cwd !== \\"string\\" || !args.cwd.trim())\\n ) {\\n throw new Error(\\n \\"fclt_setup global_and_project scope requires an explicit cwd\\"\\n );\\n }\\n return [\\n \\"setup\\",\\n \\"--json\\",\\n ...(args.scope === \\"global_and_project\\" ? [\\"--include-project\\"] : []),\\n ...(apply ? [] : [\\"--dry-run\\"]),\\n ...(args.installCodexPlugin === false ? [\\"--no-codex-plugin\\"] : []),\\n ];\\n }\\n case \\"fclt_capability\\":\\n return capabilityCommand(args);\\n case \\"fclt_workflow\\":\\n return workflowCommand(args);\\n case \\"fclt_sync\\":\\n return syncCommand(args);\\n case \\"fclt_registry\\":\\n return registryCommand(args);\\n case \\"fclt_audit\\":\\n return [\\n \\"audit\\",\\n \\"--non-interactive\\",\\n ...(args.target ? [args.target] : []),\\n ...stringFlag(\\"--severity\\", args.severity),\\n \\"--json\\",\\n ];\\n case \\"fclt_automation\\":\\n if (\\n args.action !== \\"loop_activity\\" &&\\n args.scope !== \\"global\\" &&\\n args.scope !== \\"project\\"\\n ) {\\n throw new Error(`${args.action} requires global or project scope`);\\n }\\n if (args.action === \\"autosync_status\\") {\\n return [\\n \\"autosync\\",\\n \\"status\\",\\n ...(args.tool ? [args.tool] : []),\\n ...scopeArgs(args.scope),\\n ];\\n }\\n if (args.tool) {\\n throw new Error(`${args.action} does not accept tool`);\\n }\\n if (args.action === \\"loop_status\\") {\\n return [\\"ai\\", \\"loop\\", ...scopeArgs(args.scope), \\"status\\", \\"--json\\"];\\n }\\n if (args.action === \\"loop_activity\\") {\\n return [\\n \\"ai\\",\\n \\"loop\\",\\n ...scopeArgs(args.scope),\\n \\"activity\\",\\n ...(args.scope === \\"global\\" || args.scope === \\"project\\"\\n ? []\\n : [\\"--all\\"]),\\n \\"--json\\",\\n ];\\n }\\n if (args.action === \\"loop_preview\\") {\\n return [\\n \\"ai\\",\\n \\"loop\\",\\n ...scopeArgs(args.scope),\\n \\"run\\",\\n \\"--dry-run\\",\\n \\"--json\\",\\n ];\\n }\\n throw new Error(`Unsupported automation action: ${args.action}`);\\n case \\"fclt_status\\":\\n return [\\"status\\", ...scopeArgs(args.scope), \\"--json\\"];\\n case \\"fclt_doctor\\":\\n return [\\"doctor\\", ...scopeArgs(args.scope), \\"--json\\"];\\n case \\"fclt_paths\\":\\n return [\\"paths\\", ...scopeArgs(args.scope), \\"--json\\"];\\n case \\"fclt_init_operating_model\\":\\n if (args.dryRun === false && args.approve !== true) {\\n throw new Error(\\n \\"fclt_init_operating_model apply requires approve=true\\"\\n );\\n }\\n if (args.force === true && args.approve !== true) {\\n throw new Error(\\n \\"fclt_init_operating_model force requires approve=true\\"\\n );\\n }\\n return [\\n \\"templates\\",\\n \\"init\\",\\n \\"operating-model\\",\\n ...scopeArgs(args.scope),\\n ...boolFlag(\\"--update\\", args.update),\\n ...(args.dryRun === false ? [] : [\\"--dry-run\\"]),\\n ...boolFlag(\\"--force\\", args.force),\\n \\"--json\\",\\n ];\\n case \\"fclt_writeback_add\\":\\n requireMutationApproval(name, args);\\n requireString(\\"evidence\\", args.evidence);\\n return [\\n \\"ai\\",\\n \\"writeback\\",\\n ...scopeArgs(args.scope),\\n \\"add\\",\\n \\"--kind\\",\\n args.kind,\\n \\"--summary\\",\\n args.summary,\\n ...stringFlag(\\"--asset\\", args.asset),\\n ...stringFlag(\\"--category\\", args.category),\\n ...stringFlag(\\"--details\\", args.details),\\n ...stringFlag(\\"--impact\\", args.impact),\\n ...stringFlag(\\"--attempted-workaround\\", args.attemptedWorkaround),\\n ...stringFlag(\\"--desired-outcome\\", args.desiredOutcome),\\n ...stringFlag(\\"--sensitivity\\", args.sensitivity),\\n ...stringFlag(\\"--evidence\\", args.evidence),\\n ...stringFlag(\\"--confidence\\", args.confidence),\\n \\"--json\\",\\n ];\\n case \\"fclt_writeback_review\\": {\\n const mode = args.mode || \\"list\\";\\n return [\\n \\"ai\\",\\n \\"writeback\\",\\n ...scopeArgs(args.scope),\\n mode,\\n ...stringFlag(\\"--by\\", args.by),\\n ];\\n }\\n case \\"fclt_evolve\\": {\\n const action = args.action || \\"list\\";\\n if ([\\"propose\\", \\"draft\\", \\"review\\"].includes(action)) {\\n requireMutationApproval(`fclt_evolve ${action}`, args);\\n }\\n if (action === \\"propose\\") {\\n requireString(\\"asset\\", args.asset);\\n }\\n if ([\\"draft\\", \\"review\\", \\"show\\"].includes(action)) {\\n requireString(\\"id\\", args.id);\\n }\\n return [\\n \\"ai\\",\\n \\"evolve\\",\\n ...scopeArgs(args.scope),\\n action,\\n ...(action === \\"assess\\" || action === \\"propose\\"\\n ? stringFlag(\\"--asset\\", args.asset)\\n : []),\\n ...(args.id ? [args.id] : []),\\n ...(action === \\"assess\\" ? [\\"--json\\"] : []),\\n ];\\n }\\n default:\\n throw new Error(`Unknown tool: ${name}`);\\n }\\n}\\n\\nfunction operationMetadata(name, args, command) {\\n const action =\\n name === \\"fclt_writeback_add\\"\\n ? \\"writeback_add\\"\\n : name === \\"fclt_evolve\\"\\n ? `evolve_${args.action || \\"list\\"}`\\n : args.action || name;\\n const reviewActions = new Set([\\n \\"activity_decide\\",\\n \\"writeback_add\\",\\n \\"writeback_link\\",\\n \\"writeback_disposition\\",\\n \\"evolve_propose\\",\\n \\"evolve_draft\\",\\n \\"evolve_review\\",\\n \\"evolve_verify\\",\\n \\"reconcile\\",\\n ]);\\n const preview = command.includes(\\"--dry-run\\");\\n const risk = reviewActions.has(action)\\n ? \\"review_producing\\"\\n : !preview && name === \\"fclt_setup\\"\\n ? \\"reversible_mutation\\"\\n : !preview && name === \\"fclt_init_operating_model\\"\\n ? \\"high_risk_destructive\\"\\n : \\"read_only\\";\\n return {\\n tool: name,\\n action,\\n risk,\\n scope:\\n args.scope ||\\n (name === \\"fclt_automation\\" && action === \\"loop_activity\\"\\n ? \\"all\\"\\n : \\"auto\\"),\\n target:\\n args.id ||\\n args.selector ||\\n args.asset ||\\n args.item ||\\n args.source ||\\n args.tool ||\\n args.locator ||\\n null,\\n preview,\\n };\\n}\\n\\nfunction recoveryForOperation(operation, stdout) {\\n if (operation.risk === \\"review_producing\\") {\\n return {\\n canonicalCapabilityChanged: false,\\n audit: \\"native fclt review artifacts and append-only journal\\",\\n };\\n }\\n if (\\n operation.risk !== \\"reversible_mutation\\" &&\\n operation.risk !== \\"high_risk_destructive\\"\\n ) {\\n return null;\\n }\\n const report = isPlainObject(stdout) ? stdout : {};\\n return {\\n rollbackAvailable: false,\\n changedPaths: Array.isArray(report.changedPaths) ? report.changedPaths : [],\\n skippedPaths: Array.isArray(report.skippedPaths) ? report.skippedPaths : [],\\n repairActions: Array.isArray(report.repairActions)\\n ? report.repairActions\\n : [],\\n verification:\\n operation.tool === \\"fclt_setup\\"\\n ? \\"rerun fclt_setup in preview mode and verify doctor readiness\\"\\n : \\"verify doctor, generated state, authored-file hashes, and exact target paths\\",\\n };\\n}\\n\\nasync function runFclt(args, cwd, operation) {\\n const discovery = await runtime.discoverRuntime();\\n if (!discovery.selected) {\\n return {\\n code: 1,\\n text: JSON.stringify(\\n {\\n schemaVersion: 1,\\n operation,\\n error: \\"no_compatible_runtime\\",\\n message:\\n \\"No compatible fclt runtime is available. Check, stage, and apply an explicit verified version with fclt_runtime.\\",\\n runtime: discovery,\\n },\\n null,\\n 2\\n ),\\n };\\n }\\n\\n if (\\n operation.tool === \\"fclt_audit\\" &&\\n !discovery.selected.capabilities?.includes(AUDIT_READ_ONLY_CAPABILITY)\\n ) {\\n return {\\n code: 1,\\n text: JSON.stringify(\\n {\\n schemaVersion: 1,\\n operation,\\n error: \\"missing_runtime_capability\\",\\n message:\\n \\"The selected fclt runtime does not advertise audit-read-only-v1; typed audit fails closed.\\",\\n runtime: discovery,\\n },\\n null,\\n 2\\n ),\\n };\\n }\\n\\n if (\\n operation.action === \\"activity_decide\\" &&\\n !discovery.selected.capabilities?.includes(\\n ACTIVITY_ACTION_DECIDE_CAPABILITY\\n )\\n ) {\\n return {\\n code: 1,\\n text: JSON.stringify(\\n {\\n schemaVersion: 1,\\n operation,\\n error: \\"missing_runtime_capability\\",\\n message:\\n \\"The selected fclt runtime does not advertise activity-action-decide-v1; typed activity decisions fail closed.\\",\\n runtime: discovery,\\n },\\n null,\\n 2\\n ),\\n };\\n }\\n\\n if (\\n operation.action === \\"activity_resolve\\" &&\\n !discovery.selected.capabilities?.includes(\\n ACTIVITY_ACTION_RESOLVE_CAPABILITY\\n )\\n ) {\\n return {\\n code: 1,\\n text: JSON.stringify(\\n {\\n schemaVersion: 1,\\n operation,\\n error: \\"missing_runtime_capability\\",\\n message:\\n \\"The selected fclt runtime does not advertise activity-action-resolve-v1; typed activity resolution fails closed.\\",\\n runtime: discovery,\\n },\\n null,\\n 2\\n ),\\n };\\n }\\n\\n const result = await runtime.runCommand(discovery.selected.executable, args, {\\n cwd: cwd || process.cwd(),\\n env: process.env,\\n timeoutMs: DEFAULT_TIMEOUT_MS,\\n });\\n const parsedStdout = parseJsonOrText(result.stdout.trim());\\n return {\\n code: result.code,\\n text: JSON.stringify(\\n {\\n schemaVersion: 1,\\n operation,\\n runtime: discovery.selected,\\n result: {\\n exitCode: result.code,\\n stdout: parsedStdout,\\n stderr: result.stderr,\\n },\\n verification: {\\n status: result.code === 0 ? \\"passed\\" : \\"failed\\",\\n exitCode: result.code,\\n },\\n recovery: recoveryForOperation(operation, parsedStdout),\\n },\\n null,\\n 2\\n ),\\n };\\n}\\n\\nfunction parseJsonOrText(value) {\\n if (!value) {\\n return \\"\\";\\n }\\n try {\\n return JSON.parse(value);\\n } catch {\\n return value;\\n }\\n}\\n\\nasync function handleRuntimeTool(args = {}) {\\n const action = args.action || \\"status\\";\\n if (action === \\"status\\") {\\n return await runtime.discoverRuntime();\\n }\\n if (action === \\"check\\") {\\n return await runtime.checkRuntimeUpdate();\\n }\\n if (action === \\"policy\\") {\\n return await runtime.setRuntimePolicy({\\n approve: args.approve,\\n pinnedVersion: args.pinnedVersion,\\n clearPin: args.clearPin,\\n updateChecksEnabled: args.updateChecksEnabled,\\n });\\n }\\n if (action === \\"stage\\") {\\n return await runtime.stageRuntime({\\n approve: args.approve,\\n version: args.version,\\n });\\n }\\n if (action === \\"apply\\") {\\n return await runtime.applyStagedRuntime({\\n approve: args.approve,\\n expectedSha256: args.expectedSha256,\\n version: args.version,\\n });\\n }\\n if (action === \\"rollback\\") {\\n return await runtime.rollbackRuntime({\\n approve: args.approve,\\n expectedActiveVersion: args.expectedActiveVersion,\\n });\\n }\\n throw new Error(`Unknown runtime action: ${action}`);\\n}\\n\\nfunction runtimeOperationMetadata(args, result) {\\n const action = args.action || \\"status\\";\\n const risk =\\n action === \\"status\\" || action === \\"check\\"\\n ? \\"read_only\\"\\n : action === \\"stage\\"\\n ? \\"review_producing\\"\\n : \\"high_risk_destructive\\";\\n return {\\n operation: {\\n tool: \\"fclt_runtime\\",\\n action,\\n risk,\\n scope: \\"plugin_runtime\\",\\n target:\\n args.version ||\\n args.pinnedVersion ||\\n args.expectedActiveVersion ||\\n null,\\n approved: args.approve === true,\\n },\\n verification: {\\n status: \\"passed\\",\\n activeVersion:\\n result.active?.packageVersion ||\\n result.selected?.packageVersion ||\\n null,\\n },\\n recovery:\\n action === \\"apply\\" || action === \\"rollback\\"\\n ? {\\n rollbackAvailable: result.rollbackAvailable === true,\\n previous: result.previous || null,\\n }\\n : action === \\"policy\\"\\n ? { previousPolicy: result.previous || null }\\n : null,\\n };\\n}\\n\\nlet transportFraming = \\"content-length\\";\\n\\nfunction send(message) {\\n const body = JSON.stringify(message);\\n if (transportFraming === \\"newline\\") {\\n process.stdout.write(`${body}\\\\n`);\\n return;\\n }\\n process.stdout.write(\\n `Content-Length: ${Buffer.byteLength(body)}\\\\r\\\\n\\\\r\\\\n${body}`\\n );\\n}\\n\\nasync function handle(message) {\\n if (!message || message.id == null) {\\n return;\\n }\\n\\n try {\\n if (message.method === \\"initialize\\") {\\n send({\\n jsonrpc: \\"2.0\\",\\n id: message.id,\\n result: {\\n protocolVersion: \\"2025-06-18\\",\\n capabilities: { tools: {} },\\n serverInfo: { name: \\"fclt\\", version: runtime.pluginVersion() },\\n },\\n });\\n return;\\n }\\n if (message.method === \\"tools/list\\") {\\n send({ jsonrpc: \\"2.0\\", id: message.id, result: { tools } });\\n return;\\n }\\n if (message.method === \\"tools/call\\") {\\n const { name, arguments: args = {} } = message.params || {};\\n validateToolArguments(name, args);\\n if (name === \\"fclt_runtime\\") {\\n const result = await handleRuntimeTool(args);\\n const metadata = runtimeOperationMetadata(args, result);\\n send({\\n jsonrpc: \\"2.0\\",\\n id: message.id,\\n result: {\\n isError: false,\\n content: [\\n {\\n type: \\"text\\",\\n text: JSON.stringify({ ...result, ...metadata }, null, 2),\\n },\\n ],\\n },\\n });\\n return;\\n }\\n const command = commandForTool(name, args);\\n const result = await runFclt(\\n command,\\n resolveToolCwd(name, args),\\n operationMetadata(name, args, command)\\n );\\n send({\\n jsonrpc: \\"2.0\\",\\n id: message.id,\\n result: {\\n isError: result.code !== 0,\\n content: [{ type: \\"text\\", text: result.text }],\\n },\\n });\\n return;\\n }\\n send({\\n jsonrpc: \\"2.0\\",\\n id: message.id,\\n error: { code: -32_601, message: `Method not found: ${message.method}` },\\n });\\n } catch (error) {\\n send({\\n jsonrpc: \\"2.0\\",\\n id: message.id,\\n error: {\\n code: -32_000,\\n message: error instanceof Error ? error.message : String(error),\\n },\\n });\\n }\\n}\\n\\nlet buffer = Buffer.alloc(0);\\n\\nfunction dispatch(body, framing) {\\n transportFraming = framing;\\n handle(JSON.parse(body)).catch((error) => {\\n send({\\n jsonrpc: \\"2.0\\",\\n id: null,\\n error: {\\n code: -32_000,\\n message: error instanceof Error ? error.message : String(error),\\n },\\n });\\n });\\n}\\n\\nprocess.stdin.on(\\"data\\", (chunk) => {\\n buffer = Buffer.concat([buffer, chunk]);\\n while (true) {\\n while (buffer[0] === 10 || buffer[0] === 13) {\\n buffer = buffer.subarray(1);\\n }\\n if (buffer.length === 0) {\\n return;\\n }\\n if (buffer[0] === 123 || buffer[0] === 91) {\\n const lineEnd = buffer.indexOf(\\"\\\\n\\");\\n if (lineEnd === -1) {\\n return;\\n }\\n const body = buffer.subarray(0, lineEnd).toString(\\"utf8\\").trim();\\n buffer = buffer.subarray(lineEnd + 1);\\n if (body) {\\n dispatch(body, \\"newline\\");\\n }\\n continue;\\n }\\n const headerEnd = buffer.indexOf(\\"\\\\r\\\\n\\\\r\\\\n\\");\\n if (headerEnd === -1) {\\n return;\\n }\\n const header = buffer.slice(0, headerEnd).toString(\\"utf8\\");\\n const match = CONTENT_LENGTH_RE.exec(header);\\n if (!match) {\\n buffer = Buffer.alloc(0);\\n return;\\n }\\n const length = Number(match[1]);\\n const frameEnd = headerEnd + 4 + length;\\n if (buffer.length < frameEnd) {\\n return;\\n }\\n const body = buffer.slice(headerEnd + 4, frameEnd).toString(\\"utf8\\");\\n buffer = buffer.slice(frameEnd);\\n dispatch(body, \\"content-length\\");\\n }\\n});\\n\\nif (process.argv.includes(\\"--self-test\\")) {\\n console.log(\\n JSON.stringify(\\n {\\n pluginVersion: runtime.pluginVersion(),\\n protocolVersion: runtime.PLUGIN_PROTOCOL_VERSION,\\n tools: tools.map((tool) => tool.name),\\n },\\n null,\\n 2\\n )\\n );\\n process.exit(0);\\n}\\n","scripts/fclt-runtime.cjs":"\\"use strict\\";\\n\\nconst { spawn } = require(\\"node:child_process\\");\\nconst crypto = require(\\"node:crypto\\");\\nconst fs = require(\\"node:fs\\");\\nconst fsp = require(\\"node:fs/promises\\");\\nconst https = require(\\"node:https\\");\\nconst os = require(\\"node:os\\");\\nconst path = require(\\"node:path\\");\\n\\nconst PLUGIN_PROTOCOL_VERSION = 1;\\nconst STATE_SCHEMA_VERSION = 1;\\nconst REPOSITORY = \\"hack-dance/fclt\\";\\nconst MAX_BINARY_BYTES = 256 * 1024 * 1024;\\nconst MAX_METADATA_BYTES = 2 * 1024 * 1024;\\nconst DOWNLOAD_TIMEOUT_MS = 30_000;\\nconst COMMAND_TIMEOUT_MS = 15_000;\\nconst ALLOWED_DOWNLOAD_HOSTS = new Set([\\n \\"api.github.com\\",\\n \\"github.com\\",\\n \\"objects.githubusercontent.com\\",\\n \\"release-assets.githubusercontent.com\\",\\n]);\\nconst SEMVER_RE = /^\\\\d+\\\\.\\\\d+\\\\.\\\\d+(?:[-+][0-9A-Za-z.-]+)?$/;\\nconst SHA256_RE = /^[a-f0-9]{64}$/;\\nconst NEWLINE_RE = /\\\\r?\\\\n/;\\nconst CHECKSUM_LINE_RE = /^([a-fA-F0-9]{64})\\\\s+\\\\*?(.+)$/;\\nconst WINDOWS_SHIM_RE = /\\\\.(?:bat|cmd)$/i;\\n\\nfunction isPlainObject(value) {\\n return Boolean(value) && typeof value === \\"object\\" && !Array.isArray(value);\\n}\\n\\nfunction pluginVersion() {\\n try {\\n const manifest = JSON.parse(\\n fs.readFileSync(\\n path.resolve(__dirname, \\"..\\", \\".codex-plugin\\", \\"plugin.json\\"),\\n \\"utf8\\"\\n )\\n );\\n return typeof manifest.version === \\"string\\" ? manifest.version : \\"unknown\\";\\n } catch {\\n return \\"unknown\\";\\n }\\n}\\n\\nfunction runtimeStateRoot(env = process.env, platform = process.platform) {\\n if (env.FCLT_PLUGIN_RUNTIME_DIR) {\\n return path.resolve(env.FCLT_PLUGIN_RUNTIME_DIR);\\n }\\n const home = env.HOME || env.USERPROFILE || os.homedir();\\n if (platform === \\"darwin\\") {\\n return path.join(\\n home,\\n \\"Library\\",\\n \\"Application Support\\",\\n \\"fclt\\",\\n \\"plugin-runtime\\"\\n );\\n }\\n if (platform === \\"win32\\") {\\n return path.join(\\n env.LOCALAPPDATA || path.join(home, \\"AppData\\", \\"Local\\"),\\n \\"fclt\\",\\n \\"plugin-runtime\\"\\n );\\n }\\n return path.join(\\n env.XDG_STATE_HOME || path.join(home, \\".local\\", \\"state\\"),\\n \\"fclt\\",\\n \\"plugin-runtime\\"\\n );\\n}\\n\\nfunction installStatePaths(env = process.env, platform = process.platform) {\\n const home = env.HOME || env.USERPROFILE || os.homedir();\\n const override = env.FACULT_LOCAL_STATE_DIR?.trim();\\n const portableRoot = override\\n ? path.resolve(override)\\n : platform === \\"darwin\\"\\n ? path.join(home, \\"Library\\", \\"Application Support\\", \\"fclt\\")\\n : path.join(\\n env.XDG_STATE_HOME\\n ? path.resolve(env.XDG_STATE_HOME)\\n : path.join(home, \\".local\\", \\"state\\"),\\n \\"fclt\\"\\n );\\n const candidates = [\\n path.join(portableRoot, \\"install.json\\"),\\n ...(platform === \\"win32\\"\\n ? [\\n path.join(\\n env.LOCALAPPDATA || path.join(home, \\"AppData\\", \\"Local\\"),\\n \\"fclt\\",\\n \\"install.json\\"\\n ),\\n ]\\n : []),\\n path.join(home, \\".ai\\", \\".facult\\", \\"install.json\\"),\\n path.join(home, \\".facult\\", \\"install.json\\"),\\n path.join(home, \\".local\\", \\"share\\", \\"fclt\\", \\"install.json\\"),\\n ];\\n return [...new Set(candidates.map((candidate) => path.resolve(candidate)))];\\n}\\n\\nfunction isSubpath(child, parent) {\\n const relative = path.relative(parent, child);\\n return (\\n relative === \\"\\" || !(relative.startsWith(\\"..\\") || path.isAbsolute(relative))\\n );\\n}\\n\\nasync function assertManagedPath(target, root) {\\n const resolvedRoot = path.resolve(root);\\n const resolvedTarget = path.resolve(target);\\n if (!isSubpath(resolvedTarget, resolvedRoot)) {\\n throw new Error(\\"Runtime path escapes the managed runtime root.\\");\\n }\\n\\n const relative = path.relative(resolvedRoot, path.dirname(resolvedTarget));\\n const segments = relative ? relative.split(path.sep) : [];\\n let cursor = resolvedRoot;\\n for (const segment of segments) {\\n cursor = path.join(cursor, segment);\\n try {\\n if ((await fsp.lstat(cursor)).isSymbolicLink()) {\\n throw new Error(\\"Runtime path traverses a symbolic link.\\");\\n }\\n } catch (error) {\\n if (error && error.code === \\"ENOENT\\") {\\n continue;\\n }\\n throw error;\\n }\\n }\\n return resolvedTarget;\\n}\\n\\nasync function readJson(pathValue) {\\n try {\\n const value = JSON.parse(await fsp.readFile(pathValue, \\"utf8\\"));\\n return isPlainObject(value) ? value : null;\\n } catch {\\n return null;\\n }\\n}\\n\\nasync function runtimePolicy(options = {}) {\\n const root = runtimeStateRoot(options.env, options.platform);\\n const persisted = await readJson(path.join(root, \\"policy.json\\"));\\n return {\\n schemaVersion: STATE_SCHEMA_VERSION,\\n updateChecksEnabled: persisted?.updateChecksEnabled !== false,\\n pinnedVersion:\\n typeof persisted?.pinnedVersion === \\"string\\" &&\\n persisted.pinnedVersion.trim()\\n ? normalizeVersion(persisted.pinnedVersion)\\n : null,\\n };\\n}\\n\\nfunction commandNames(platform = process.platform) {\\n return platform === \\"win32\\"\\n ? [\\"fclt.exe\\", \\"fclt.cmd\\", \\"facult.exe\\", \\"facult.cmd\\"]\\n : [\\"fclt\\", \\"facult\\"];\\n}\\n\\nfunction pathCandidates(env = process.env, platform = process.platform) {\\n const values = [];\\n for (const directory of (env.PATH || \\"\\").split(path.delimiter)) {\\n if (!directory) {\\n continue;\\n }\\n for (const name of commandNames(platform)) {\\n values.push(path.join(directory, name));\\n }\\n }\\n return values;\\n}\\n\\nfunction systemPathCandidates(env = process.env, platform = process.platform) {\\n if (Object.hasOwn(env, \\"FCLT_SYSTEM_PATHS\\")) {\\n return (env.FCLT_SYSTEM_PATHS || \\"\\").split(path.delimiter).filter(Boolean);\\n }\\n if (platform === \\"darwin\\") {\\n return [\\"/opt/homebrew/bin/fclt\\", \\"/usr/local/bin/fclt\\"];\\n }\\n if (platform === \\"win32\\") {\\n return [];\\n }\\n return [\\"/usr/local/bin/fclt\\", \\"/usr/bin/fclt\\"];\\n}\\n\\nfunction candidateSource(candidate) {\\n const normalized = candidate.split(\\"\\\\\\\\\\").join(\\"/\\");\\n if (normalized.includes(\\"/plugin-runtime/versions/\\")) {\\n return \\"plugin_runtime\\";\\n }\\n if (normalized.includes(\\"/mise/\\") || normalized.includes(\\"/mise/installs/\\")) {\\n return \\"mise\\";\\n }\\n if (\\n normalized.includes(\\"/Cellar/\\") ||\\n normalized.startsWith(\\"/opt/homebrew/\\")\\n ) {\\n return \\"homebrew\\";\\n }\\n if (normalized.includes(\\"/node_modules/\\") || normalized.includes(\\"/npm/\\")) {\\n return \\"npm\\";\\n }\\n if (normalized.includes(\\"/.ai/.facult/bin/\\")) {\\n return \\"canonical_install\\";\\n }\\n return \\"path\\";\\n}\\n\\nasync function activeRuntimeCandidate(root) {\\n const active = await readJson(path.join(root, \\"active.json\\"));\\n if (typeof active?.executable !== \\"string\\" || !active.executable.trim()) {\\n return null;\\n }\\n const executable = path.resolve(active.executable);\\n if (!isSubpath(executable, path.join(root, \\"versions\\"))) {\\n return null;\\n }\\n return {\\n executable,\\n source: \\"plugin_runtime\\",\\n expectedSha256: active.sha256,\\n active,\\n };\\n}\\n\\nasync function persistedInstallCandidates(\\n env = process.env,\\n platform = process.platform\\n) {\\n const candidates = [];\\n for (const statePath of installStatePaths(env, platform)) {\\n const state = await readJson(statePath);\\n if (typeof state?.binaryPath === \\"string\\" && state.binaryPath.trim()) {\\n candidates.push({\\n executable: path.resolve(state.binaryPath),\\n source:\\n typeof state.source === \\"string\\" ? state.source : \\"install_metadata\\",\\n installStatePath: statePath,\\n });\\n }\\n }\\n return candidates;\\n}\\n\\nasync function runtimeCandidates(options = {}) {\\n const env = options.env || process.env;\\n const platform = options.platform || process.platform;\\n const home = env.HOME || env.USERPROFILE || os.homedir();\\n const root = runtimeStateRoot(env, platform);\\n const candidates = [];\\n let configuredPathCandidate = null;\\n\\n if (env.FCLT_BIN?.trim()) {\\n const explicit = env.FCLT_BIN.trim();\\n if (path.isAbsolute(explicit) || explicit.includes(path.sep)) {\\n candidates.push({\\n executable: path.resolve(explicit),\\n source: \\"explicit\\",\\n });\\n } else {\\n const resolved = pathCandidates(env, platform).find(\\n (candidate) =>\\n path.basename(candidate) === explicit && fs.existsSync(candidate)\\n );\\n configuredPathCandidate = {\\n executable: resolved || explicit,\\n source: \\"configured_path\\",\\n };\\n }\\n }\\n\\n const active = await activeRuntimeCandidate(root);\\n if (active) {\\n candidates.push(active);\\n }\\n if (configuredPathCandidate) {\\n candidates.push(configuredPathCandidate);\\n }\\n candidates.push(...(await persistedInstallCandidates(env, platform)));\\n candidates.push(\\n ...pathCandidates(env, platform).map((executable) => ({\\n executable,\\n source: candidateSource(executable),\\n }))\\n );\\n\\n for (const executable of [\\n path.join(home, \\".ai\\", \\".facult\\", \\"bin\\", commandNames(platform)[0]),\\n ...systemPathCandidates(env, platform),\\n ]) {\\n candidates.push({ executable, source: candidateSource(executable) });\\n }\\n\\n const unique = [];\\n const seen = new Set();\\n for (const candidate of candidates) {\\n const key = path.resolve(candidate.executable);\\n if (!seen.has(key)) {\\n seen.add(key);\\n unique.push(candidate);\\n }\\n }\\n return unique;\\n}\\n\\nfunction runCommand(executable, args, options = {}) {\\n return new Promise((resolve) => {\\n let child;\\n try {\\n const platform = options.platform || process.platform;\\n const windowsShim =\\n platform === \\"win32\\" && WINDOWS_SHIM_RE.test(executable);\\n const command = windowsShim\\n ? options.env?.ComSpec || process.env.ComSpec || \\"cmd.exe\\"\\n : executable;\\n const commandArgs = windowsShim\\n ? [\\n \\"/d\\",\\n \\"/v:off\\",\\n \\"/s\\",\\n \\"/c\\",\\n [executable, ...args]\\n .map(\\n (value) =>\\n `\\"${String(value)\\n .replaceAll(\\"%\\", \\"%%\\")\\n .replace(/[\\\\^&|<>()!\\"]/g, \\"^$&\\")}\\"`\\n )\\n .join(\\" \\"),\\n ]\\n : args;\\n child = spawn(command, commandArgs, {\\n cwd: options.cwd || process.cwd(),\\n env: options.env || process.env,\\n stdio: [\\"ignore\\", \\"pipe\\", \\"pipe\\"],\\n });\\n } catch (error) {\\n resolve({ code: 1, stdout: \\"\\", stderr: error.message });\\n return;\\n }\\n let stdout = \\"\\";\\n let stderr = \\"\\";\\n let settled = false;\\n const timer = setTimeout(\\n () => child.kill(\\"SIGTERM\\"),\\n options.timeoutMs || COMMAND_TIMEOUT_MS\\n );\\n const finish = (code, error) => {\\n if (settled) {\\n return;\\n }\\n settled = true;\\n clearTimeout(timer);\\n resolve({\\n code,\\n stdout,\\n stderr: [stderr.trim(), error].filter(Boolean).join(\\"\\\\n\\"),\\n });\\n };\\n child.stdout.on(\\"data\\", (chunk) => {\\n stdout += chunk.toString();\\n if (stdout.length > MAX_METADATA_BYTES) {\\n child.kill(\\"SIGTERM\\");\\n }\\n });\\n child.stderr.on(\\"data\\", (chunk) => {\\n stderr += chunk.toString();\\n if (stderr.length > MAX_METADATA_BYTES) {\\n child.kill(\\"SIGTERM\\");\\n }\\n });\\n child.on(\\"error\\", (error) => finish(1, error.message));\\n child.on(\\"close\\", (code) => finish(code ?? 1));\\n });\\n}\\n\\nfunction parseProtocolReport(raw) {\\n try {\\n const report = JSON.parse(raw);\\n if (\\n !isPlainObject(report) ||\\n report.schemaVersion !== 1 ||\\n typeof report.packageVersion !== \\"string\\" ||\\n !isPlainObject(report.protocol) ||\\n !Number.isInteger(report.protocol.version) ||\\n !Number.isInteger(report.protocol.minimumPluginVersion) ||\\n !Number.isInteger(report.protocol.maximumPluginVersion) ||\\n (report.capabilities !== undefined &&\\n (!Array.isArray(report.capabilities) ||\\n report.capabilities.some((value) => typeof value !== \\"string\\")))\\n ) {\\n return null;\\n }\\n return report;\\n } catch {\\n return null;\\n }\\n}\\n\\nfunction protocolCompatibility(report) {\\n if (!report) {\\n return { compatible: false, reason: \\"missing_protocol_handshake\\" };\\n }\\n const compatible =\\n report.protocol.minimumPluginVersion <= PLUGIN_PROTOCOL_VERSION &&\\n report.protocol.maximumPluginVersion >= PLUGIN_PROTOCOL_VERSION;\\n return {\\n compatible,\\n reason: compatible ? \\"compatible\\" : \\"protocol_version_skew\\",\\n };\\n}\\n\\nasync function inspectCandidate(candidate, options = {}) {\\n const executable = path.resolve(candidate.executable);\\n try {\\n const stat = await fsp.stat(executable);\\n if (!stat.isFile()) {\\n return {\\n ...candidate,\\n executable,\\n available: false,\\n compatible: false,\\n reason: \\"not_a_file\\",\\n };\\n }\\n } catch {\\n return {\\n ...candidate,\\n executable,\\n available: false,\\n compatible: false,\\n reason: \\"not_found\\",\\n };\\n }\\n\\n if (candidate.source === \\"plugin_runtime\\") {\\n if (\\n typeof candidate.expectedSha256 !== \\"string\\" ||\\n !SHA256_RE.test(candidate.expectedSha256)\\n ) {\\n return {\\n ...candidate,\\n executable,\\n available: true,\\n compatible: false,\\n reason: \\"missing_checksum\\",\\n };\\n }\\n try {\\n const actualSha256 = sha256(await fsp.readFile(executable));\\n if (actualSha256 !== candidate.expectedSha256) {\\n return {\\n ...candidate,\\n executable,\\n available: true,\\n compatible: false,\\n reason: \\"checksum_mismatch\\",\\n };\\n }\\n } catch {\\n return {\\n ...candidate,\\n executable,\\n available: false,\\n compatible: false,\\n reason: \\"checksum_unreadable\\",\\n };\\n }\\n }\\n\\n const result = await runCommand(executable, [\\"protocol\\", \\"--json\\"], options);\\n const report =\\n result.code === 0 ? parseProtocolReport(result.stdout.trim()) : null;\\n const compatibility = protocolCompatibility(report);\\n return {\\n ...candidate,\\n executable,\\n available: true,\\n compatible: compatibility.compatible,\\n reason: compatibility.reason,\\n packageVersion: report?.packageVersion,\\n protocol: report?.protocol,\\n platform: report?.runtime?.platform,\\n architecture: report?.runtime?.architecture,\\n capabilities: report?.capabilities || [],\\n };\\n}\\n\\nasync function discoverRuntime(options = {}) {\\n const policy = await runtimePolicy(options);\\n const inspected = [];\\n for (const candidate of await runtimeCandidates(options)) {\\n const result = await inspectCandidate(candidate, options);\\n inspected.push(result);\\n if (result.compatible) {\\n return {\\n schemaVersion: STATE_SCHEMA_VERSION,\\n plugin: {\\n version: pluginVersion(),\\n protocolVersion: PLUGIN_PROTOCOL_VERSION,\\n },\\n policy,\\n selected: result,\\n compatible: true,\\n requiresFreshSession: false,\\n candidates: inspected,\\n };\\n }\\n }\\n return {\\n schemaVersion: STATE_SCHEMA_VERSION,\\n plugin: {\\n version: pluginVersion(),\\n protocolVersion: PLUGIN_PROTOCOL_VERSION,\\n },\\n policy,\\n selected: null,\\n compatible: false,\\n requiresFreshSession: false,\\n candidates: inspected,\\n };\\n}\\n\\nfunction releaseTarget(\\n platform = process.platform,\\n architecture = process.arch\\n) {\\n if (\\n platform === \\"darwin\\" &&\\n (architecture === \\"arm64\\" || architecture === \\"x64\\")\\n ) {\\n return { platform: \\"darwin\\", architecture, extension: \\"\\" };\\n }\\n if (platform === \\"linux\\" && architecture === \\"x64\\") {\\n return { platform: \\"linux\\", architecture, extension: \\"\\" };\\n }\\n if (platform === \\"win32\\" && architecture === \\"x64\\") {\\n return { platform: \\"windows\\", architecture, extension: \\".exe\\" };\\n }\\n throw new Error(\\n `Unsupported plugin runtime target: ${platform}/${architecture}`\\n );\\n}\\n\\nfunction normalizeVersion(version) {\\n const normalized = version?.startsWith(\\"v\\") ? version.slice(1) : version;\\n if (!(normalized && SEMVER_RE.test(normalized))) {\\n throw new Error(\\"Runtime version must be an explicit semantic version.\\");\\n }\\n return normalized;\\n}\\n\\nfunction assertAllowedUrl(urlValue) {\\n const url = new URL(urlValue);\\n if (url.protocol !== \\"https:\\" || !ALLOWED_DOWNLOAD_HOSTS.has(url.hostname)) {\\n throw new Error(\\n \\"Runtime downloads are restricted to approved HTTPS release hosts.\\"\\n );\\n }\\n return url;\\n}\\n\\nfunction downloadBuffer(urlValue, options = {}) {\\n const maxBytes = options.maxBytes || MAX_METADATA_BYTES;\\n const redirectsRemaining = options.redirectsRemaining ?? 5;\\n const url = assertAllowedUrl(urlValue);\\n return new Promise((resolve, reject) => {\\n const request = https.get(\\n url,\\n {\\n headers: {\\n accept: options.accept || \\"application/octet-stream\\",\\n \\"user-agent\\": \\"fclt-codex-plugin\\",\\n },\\n },\\n (response) => {\\n if (\\n response.statusCode &&\\n response.statusCode >= 300 &&\\n response.statusCode < 400 &&\\n response.headers.location\\n ) {\\n response.resume();\\n if (redirectsRemaining <= 0) {\\n reject(new Error(\\"Runtime download exceeded the redirect limit.\\"));\\n return;\\n }\\n const redirected = new URL(response.headers.location, url).toString();\\n downloadBuffer(redirected, {\\n ...options,\\n redirectsRemaining: redirectsRemaining - 1,\\n }).then(resolve, reject);\\n return;\\n }\\n if (response.statusCode !== 200) {\\n response.resume();\\n reject(\\n new Error(\\n `Runtime download failed with HTTP ${response.statusCode}.`\\n )\\n );\\n return;\\n }\\n const declaredLength = Number(response.headers[\\"content-length\\"] || 0);\\n if (declaredLength > maxBytes) {\\n response.resume();\\n reject(new Error(\\"Runtime download exceeds the allowed size.\\"));\\n return;\\n }\\n const chunks = [];\\n let total = 0;\\n response.on(\\"data\\", (chunk) => {\\n total += chunk.length;\\n if (total > maxBytes) {\\n request.destroy(\\n new Error(\\"Runtime download exceeds the allowed size.\\")\\n );\\n return;\\n }\\n chunks.push(chunk);\\n });\\n response.on(\\"end\\", () => resolve(Buffer.concat(chunks)));\\n }\\n );\\n request.setTimeout(options.timeoutMs || DOWNLOAD_TIMEOUT_MS, () => {\\n request.destroy(new Error(\\"Runtime download timed out.\\"));\\n });\\n request.on(\\"error\\", reject);\\n });\\n}\\n\\nfunction sha256(bytes) {\\n return crypto.createHash(\\"sha256\\").update(bytes).digest(\\"hex\\");\\n}\\n\\nfunction checksumForAsset(checksums, assetName) {\\n for (const line of checksums.split(NEWLINE_RE)) {\\n const match = CHECKSUM_LINE_RE.exec(line.trim());\\n if (match?.[2] === assetName) {\\n return match[1].toLowerCase();\\n }\\n }\\n throw new Error(`Published checksums do not include ${assetName}.`);\\n}\\n\\nasync function withMutationLock(root, action) {\\n await fsp.mkdir(root, { recursive: true, mode: 0o700 });\\n const lockPath = await assertManagedPath(\\n path.join(root, \\"mutation.lock\\"),\\n root\\n );\\n let handle;\\n try {\\n handle = await fsp.open(lockPath, \\"wx\\", 0o600);\\n } catch (error) {\\n if (error?.code === \\"EEXIST\\") {\\n throw new Error(\\n \\"Another fclt plugin runtime mutation is already in progress.\\"\\n );\\n }\\n throw error;\\n }\\n try {\\n return await action();\\n } finally {\\n await handle.close();\\n await fsp.rm(lockPath, { force: true });\\n }\\n}\\n\\nasync function writeJsonAtomic(pathValue, value, root) {\\n const target = await assertManagedPath(pathValue, root);\\n await fsp.mkdir(path.dirname(target), { recursive: true, mode: 0o700 });\\n const temporary = `${target}.tmp-${crypto.randomUUID()}`;\\n await fsp.writeFile(temporary, `${JSON.stringify(value, null, 2)}\\\\n`, {\\n mode: 0o600,\\n });\\n await fsp.rename(temporary, target);\\n}\\n\\nfunction releaseUrls(version, target) {\\n const tag = `v${version}`;\\n const assetName = `fclt-${version}-${target.platform}-${target.architecture}${target.extension}`;\\n const base = `https://github.com/${REPOSITORY}/releases/download/${tag}`;\\n return {\\n tag,\\n assetName,\\n binaryUrl: `${base}/${assetName}`,\\n checksumUrl: `${base}/SHA256SUMS`,\\n };\\n}\\n\\nfunction releaseMetadataUrl(version) {\\n return `https://api.github.com/repos/${REPOSITORY}/releases/tags/v${version}`;\\n}\\n\\nfunction releaseAssets(metadata, version, target) {\\n if (\\n !isPlainObject(metadata) ||\\n metadata.tag_name !== `v${version}` ||\\n !Array.isArray(metadata.assets)\\n ) {\\n throw new Error(\\n \\"Release metadata does not match the requested immutable tag.\\"\\n );\\n }\\n const expected = releaseUrls(version, target);\\n const findAsset = (name) =>\\n metadata.assets.find(\\n (asset) =>\\n isPlainObject(asset) &&\\n asset.name === name &&\\n typeof asset.browser_download_url === \\"string\\"\\n );\\n const binary = findAsset(expected.assetName);\\n const checksums = findAsset(\\"SHA256SUMS\\");\\n if (!(binary && checksums)) {\\n throw new Error(\\n \\"Release metadata is missing the required runtime or checksum asset.\\"\\n );\\n }\\n assertAllowedUrl(binary.browser_download_url);\\n assertAllowedUrl(checksums.browser_download_url);\\n return { binary, checksums, expected };\\n}\\n\\nfunction verifyPublishedDigest(asset, bytes) {\\n if (typeof asset.digest !== \\"string\\" || !asset.digest.trim()) {\\n return null;\\n }\\n const [algorithm, expected] = asset.digest.toLowerCase().split(\\":\\");\\n if (algorithm !== \\"sha256\\" || !SHA256_RE.test(expected || \\"\\")) {\\n throw new Error(`Release asset ${asset.name} has an unsupported digest.`);\\n }\\n const actual = sha256(bytes);\\n if (actual !== expected) {\\n throw new Error(\\n `Release asset ${asset.name} does not match its published digest.`\\n );\\n }\\n return asset.digest.toLowerCase();\\n}\\n\\nasync function resolveLatestVersion(fetchBuffer = downloadBuffer) {\\n const bytes = await fetchBuffer(\\n `https://api.github.com/repos/${REPOSITORY}/releases/latest`,\\n {\\n maxBytes: MAX_METADATA_BYTES,\\n accept: \\"application/vnd.github+json\\",\\n }\\n );\\n const metadata = JSON.parse(bytes.toString(\\"utf8\\"));\\n if (!isPlainObject(metadata) || typeof metadata.tag_name !== \\"string\\") {\\n throw new Error(\\"Latest release metadata did not include a tag.\\");\\n }\\n return normalizeVersion(metadata.tag_name);\\n}\\n\\nasync function checkRuntimeUpdate(options = {}) {\\n const discovery = await discoverRuntime(options);\\n if (!discovery.policy.updateChecksEnabled) {\\n return {\\n schemaVersion: STATE_SCHEMA_VERSION,\\n action: \\"check\\",\\n skipped: true,\\n reason: \\"update_checks_disabled\\",\\n currentVersion: discovery.selected?.packageVersion || null,\\n pinnedVersion: discovery.policy.pinnedVersion,\\n mutates: false,\\n };\\n }\\n const latestVersion =\\n discovery.policy.pinnedVersion ||\\n (await resolveLatestVersion(options.fetchBuffer || downloadBuffer));\\n return {\\n schemaVersion: STATE_SCHEMA_VERSION,\\n action: \\"check\\",\\n currentVersion: discovery.selected?.packageVersion || null,\\n latestVersion,\\n channel: discovery.policy.pinnedVersion ? \\"pinned\\" : \\"latest\\",\\n updateAvailable: discovery.selected?.packageVersion !== latestVersion,\\n selected: discovery.selected,\\n mutates: false,\\n };\\n}\\n\\nasync function stageRuntime(options) {\\n if (options.approve !== true) {\\n throw new Error(\\"Staging a runtime download requires approve=true.\\");\\n }\\n const version = normalizeVersion(options.version);\\n const target = releaseTarget(options.platform, options.architecture);\\n const root = runtimeStateRoot(options.env, options.platform);\\n const fetchBuffer = options.fetchBuffer || downloadBuffer;\\n const urls = releaseUrls(version, target);\\n const policy = await runtimePolicy(options);\\n if (policy.pinnedVersion && policy.pinnedVersion !== version) {\\n throw new Error(`Runtime policy is pinned to ${policy.pinnedVersion}.`);\\n }\\n\\n return await withMutationLock(root, async () => {\\n const metadataBytes = await fetchBuffer(releaseMetadataUrl(version), {\\n maxBytes: MAX_METADATA_BYTES,\\n accept: \\"application/vnd.github+json\\",\\n });\\n const metadata = JSON.parse(metadataBytes.toString(\\"utf8\\"));\\n const assets = releaseAssets(metadata, version, target);\\n const [checksumBytes, binaryBytes] = await Promise.all([\\n fetchBuffer(assets.checksums.browser_download_url, {\\n maxBytes: MAX_METADATA_BYTES,\\n }),\\n fetchBuffer(assets.binary.browser_download_url, {\\n maxBytes: MAX_BINARY_BYTES,\\n }),\\n ]);\\n const checksumDigest = verifyPublishedDigest(\\n assets.checksums,\\n checksumBytes\\n );\\n const binaryDigest = verifyPublishedDigest(assets.binary, binaryBytes);\\n const expectedSha256 = checksumForAsset(\\n checksumBytes.toString(\\"utf8\\"),\\n urls.assetName\\n );\\n const actualSha256 = sha256(binaryBytes);\\n if (expectedSha256 !== actualSha256) {\\n throw new Error(\\n \\"Downloaded runtime checksum does not match the published SHA256SUMS entry.\\"\\n );\\n }\\n\\n const stageDir = await assertManagedPath(\\n path.join(root, \\"staged\\", version),\\n root\\n );\\n await fsp.rm(stageDir, { recursive: true, force: true });\\n await fsp.mkdir(stageDir, { recursive: true, mode: 0o700 });\\n const executable = await assertManagedPath(\\n path.join(stageDir, target.platform === \\"windows\\" ? \\"fclt.exe\\" : \\"fclt\\"),\\n root\\n );\\n await fsp.writeFile(executable, binaryBytes, { mode: 0o700 });\\n if (target.platform !== \\"windows\\") {\\n await fsp.chmod(executable, 0o700);\\n }\\n\\n const inspected = await inspectCandidate(\\n { executable, source: \\"staged_plugin_runtime\\" },\\n { env: options.env, timeoutMs: options.timeoutMs }\\n );\\n if (!inspected.compatible || inspected.packageVersion !== version) {\\n await fsp.rm(stageDir, { recursive: true, force: true });\\n throw new Error(\\n \\"Staged runtime failed version or protocol verification.\\"\\n );\\n }\\n\\n const manifest = {\\n schemaVersion: STATE_SCHEMA_VERSION,\\n version,\\n tag: urls.tag,\\n assetName: urls.assetName,\\n executable,\\n sha256: actualSha256,\\n source: {\\n repository: REPOSITORY,\\n releaseMetadataUrl: releaseMetadataUrl(version),\\n binaryUrl: assets.binary.browser_download_url,\\n binaryAssetId: assets.binary.id ?? null,\\n binaryDigest,\\n checksumUrl: assets.checksums.browser_download_url,\\n checksumAssetId: assets.checksums.id ?? null,\\n checksumDigest,\\n },\\n protocol: inspected.protocol,\\n platform: target.platform,\\n architecture: target.architecture,\\n stagedAt: new Date().toISOString(),\\n };\\n await writeJsonAtomic(path.join(stageDir, \\"manifest.json\\"), manifest, root);\\n return { action: \\"stage\\", mutatesActiveRuntime: false, manifest };\\n });\\n}\\n\\nasync function verifyManifestExecutable(manifest, root, expectedParent) {\\n if (\\n !isPlainObject(manifest) ||\\n manifest.schemaVersion !== STATE_SCHEMA_VERSION ||\\n typeof manifest.version !== \\"string\\" ||\\n typeof manifest.executable !== \\"string\\" ||\\n typeof manifest.sha256 !== \\"string\\" ||\\n !SHA256_RE.test(manifest.sha256)\\n ) {\\n throw new Error(\\"Runtime manifest is missing required verification data.\\");\\n }\\n const executable = path.resolve(manifest.executable);\\n if (!isSubpath(executable, expectedParent)) {\\n throw new Error(\\n \\"Runtime manifest executable escapes its expected directory.\\"\\n );\\n }\\n await assertManagedPath(executable, root);\\n const bytes = await fsp.readFile(executable);\\n if (sha256(bytes) !== manifest.sha256) {\\n throw new Error(\\"Runtime manifest checksum does not match its executable.\\");\\n }\\n const inspected = await inspectCandidate({\\n executable,\\n source: \\"plugin_runtime\\",\\n expectedSha256: manifest.sha256,\\n });\\n if (!inspected.compatible || inspected.packageVersion !== manifest.version) {\\n throw new Error(\\n \\"Runtime manifest executable failed protocol verification.\\"\\n );\\n }\\n return inspected;\\n}\\n\\nasync function setRuntimePolicy(options = {}) {\\n if (options.approve !== true) {\\n throw new Error(\\"Changing runtime update policy requires approve=true.\\");\\n }\\n const root = runtimeStateRoot(options.env, options.platform);\\n return await withMutationLock(root, async () => {\\n const current = await runtimePolicy(options);\\n const next = {\\n schemaVersion: STATE_SCHEMA_VERSION,\\n updateChecksEnabled:\\n typeof options.updateChecksEnabled === \\"boolean\\"\\n ? options.updateChecksEnabled\\n : current.updateChecksEnabled,\\n pinnedVersion: options.clearPin\\n ? null\\n : options.pinnedVersion\\n ? normalizeVersion(options.pinnedVersion)\\n : current.pinnedVersion,\\n updatedAt: new Date().toISOString(),\\n };\\n await writeJsonAtomic(path.join(root, \\"policy.json\\"), next, root);\\n return { action: \\"policy\\", previous: current, policy: next };\\n });\\n}\\n\\nasync function applyStagedRuntime(options) {\\n if (options.approve !== true) {\\n throw new Error(\\"Applying a runtime requires approve=true.\\");\\n }\\n const version = normalizeVersion(options.version);\\n const root = runtimeStateRoot(options.env, options.platform);\\n return await withMutationLock(root, async () => {\\n const stageDir = path.join(root, \\"staged\\", version);\\n const manifest = await readJson(path.join(stageDir, \\"manifest.json\\"));\\n if (!manifest) {\\n throw new Error(`No staged runtime exists for ${version}.`);\\n }\\n if (options.expectedSha256 !== manifest.sha256) {\\n throw new Error(\\n \\"Staged runtime precondition failed: expected checksum changed.\\"\\n );\\n }\\n await verifyManifestExecutable(manifest, root, stageDir);\\n\\n const activePath = path.join(root, \\"active.json\\");\\n const previous = await readJson(activePath);\\n const versionDir = await assertManagedPath(\\n path.join(root, \\"versions\\", version),\\n root\\n );\\n await fsp.mkdir(versionDir, { recursive: true, mode: 0o700 });\\n const executable = await assertManagedPath(\\n path.join(versionDir, path.basename(manifest.executable)),\\n root\\n );\\n const temporary = `${executable}.tmp-${crypto.randomUUID()}`;\\n await fsp.copyFile(manifest.executable, temporary);\\n if (process.platform !== \\"win32\\") {\\n await fsp.chmod(temporary, 0o700);\\n }\\n await fsp.rename(temporary, executable);\\n const activeManifest = {\\n ...manifest,\\n executable,\\n activatedAt: new Date().toISOString(),\\n previous:\\n typeof previous?.version === \\"string\\" &&\\n typeof previous?.executable === \\"string\\"\\n ? {\\n version: previous.version,\\n executable: previous.executable,\\n sha256: previous.sha256,\\n }\\n : null,\\n };\\n await writeJsonAtomic(activePath, activeManifest, root);\\n const inspected = await verifyManifestExecutable(\\n activeManifest,\\n root,\\n versionDir\\n );\\n return {\\n action: \\"apply\\",\\n active: inspected,\\n previous: activeManifest.previous,\\n rollbackAvailable: Boolean(activeManifest.previous),\\n requiresFreshSession: false,\\n };\\n });\\n}\\n\\nasync function rollbackRuntime(options = {}) {\\n if (options.approve !== true) {\\n throw new Error(\\"Rolling back a runtime requires approve=true.\\");\\n }\\n const root = runtimeStateRoot(options.env, options.platform);\\n return await withMutationLock(root, async () => {\\n const activePath = path.join(root, \\"active.json\\");\\n const active = await readJson(activePath);\\n if (!isPlainObject(active?.previous)) {\\n throw new Error(\\n \\"The active plugin runtime does not have a retained rollback target.\\"\\n );\\n }\\n if (\\n options.expectedActiveVersion &&\\n options.expectedActiveVersion !== active.version\\n ) {\\n throw new Error(\\n \\"Runtime rollback precondition failed: active version changed.\\"\\n );\\n }\\n const previous = {\\n schemaVersion: STATE_SCHEMA_VERSION,\\n version: active.previous.version,\\n executable: active.previous.executable,\\n sha256: active.previous.sha256,\\n previous: {\\n version: active.version,\\n executable: active.executable,\\n sha256: active.sha256,\\n },\\n rolledBackAt: new Date().toISOString(),\\n };\\n const previousDir = path.dirname(path.resolve(previous.executable));\\n const inspected = await verifyManifestExecutable(\\n previous,\\n root,\\n previousDir\\n );\\n await writeJsonAtomic(activePath, previous, root);\\n return {\\n action: \\"rollback\\",\\n active: inspected,\\n rolledBackFrom: active.version,\\n rollbackAvailable: true,\\n requiresFreshSession: false,\\n };\\n });\\n}\\n\\nmodule.exports = {\\n PLUGIN_PROTOCOL_VERSION,\\n applyStagedRuntime,\\n assertManagedPath,\\n checkRuntimeUpdate,\\n checksumForAsset,\\n discoverRuntime,\\n downloadBuffer,\\n normalizeVersion,\\n parseProtocolReport,\\n pluginVersion,\\n protocolCompatibility,\\n releaseTarget,\\n rollbackRuntime,\\n runCommand,\\n runtimeCandidates,\\n runtimePolicy,\\n runtimeStateRoot,\\n setRuntimePolicy,\\n sha256,\\n stageRuntime,\\n};\\n","skills/fclt-capability-review/SKILL.md":"---\\nname: fclt-capability-review\\ndescription: Inspect fclt capability roots, docs, snippets, skills, agents, MCP, and automations.\\ntags: [fclt, capability, review, inventory]\\n---\\n\\n# fclt-capability-review\\n\\n## When To Use\\nUse this skill when Codex needs to understand what capability exists before changing it.\\n\\nUse it for:\\n\\n- checking global and project `.ai` roots\\n- finding relevant skills, snippets, instructions, agents, MCP servers, or automations\\n- deciding whether a change belongs in global or project scope\\n- checking whether managed rendering is enabled or needed\\n- reviewing public/private boundaries before publishing docs or pack assets\\n\\n## Workflow\\n\\n```bash\\nfclt status --json\\nfclt inventory --json\\nfclt list skills\\nfclt list instructions\\nfclt list snippets\\nfclt graph AGENTS.global.md\\n```\\n\\nFor project work:\\n\\n```bash\\nfclt status --project --json\\nfclt inventory --project --json\\n```\\n\\n## Rules\\n\\n- Read existing repo guidance before proposing project capability.\\n- Use project scope for repo-specific commands, tests, architecture, or team workflow.\\n- Use global scope only for broadly reusable behavior.\\n- Keep generated state and review artifacts out of repo-local `.ai`.\\n- Prefer adding or updating the smallest unit: instruction, snippet, skill, agent, MCP config, or automation.\\n- Treat engineering implementation as task work. Do not force product changes\\n through capability evolution merely because evolution tools exist.\\n- Before recommending mutation, state the observed problem, evidence, why the\\n proposed capability is the correct target, risk class, expected outcome,\\n verification plan, assumptions, and recovery route.\\n- Never silently overwrite authored capability or confuse generated/rendered\\n output with canonical source.\\n\\n## Output\\n\\n- capability roots found\\n- relevant assets\\n- scope recommendation\\n- missing or stale capability\\n- safe next command\\n- deliberately withheld operations or missing safer API\\n- verification and undo path for any proposed mutation\\n\\n## Authorization and evidence boundaries\\n\\nUse existing authorization for the same action and scope; do not request the same approval again.\\nHonor read-only and proposal-only requests by returning findings or a proposed writeback without\\nrecording state. When review-state mutation is authorized, prepare the smallest evidence-backed draft.\\nDrafting or recording does not authorize canonical apply, cross-scope promotion, external messages,\\ntracker changes, publishing, or scheduler activation. Preserve those gates and native authorization.\\nDo not infer effectiveness from apply: verify the producing workflow before resolving source writebacks.\\nKeep unchanged, non-actionable scheduled results quiet unless periodic reports were requested.\\n\\nFor a versioned capability registry, use `fclt capability inventory` with explicit source/target/state roots and host/platform overlays. Review source pins, ownership, drift, credential-reference availability, and retained disabled targets. Independent native records describe intent, not observed version or readiness. A successful file deployment does not prove provider discovery or execution. Preserve both sides of a conflict; do not repair it with an upstream installer in a managed output directory.\\n","skills/fclt-evolution/SKILL.md":"---\\nname: fclt-evolution\\ndescription: Turn repeated fclt writebacks into reviewed capability changes.\\ntags: [fclt, evolution, proposals, capability]\\n---\\n\\n# fclt-evolution\\n\\n## When To Use\\nUse this skill when repeated writebacks, stale canonical assets, or a clearly missing capability should become a concrete proposal.\\n\\nDo not use it for a single weak preference or speculative idea.\\n\\n## Authorization\\n\\nProceed with recording or drafting when the current request authorizes that action and scope;\\ndo not request the same approval again. For read-only or proposal-only work, return findings or a\\nproposed writeback without changing state. Strong evidence alone does not grant write authority.\\n\\nRecording or drafting does not authorize canonical application, cross-scope promotion, external\\nmessages, tracker changes, publishing, or scheduler activation. Preserve those separate gates and\\nnative authorization. Keep unchanged, non-actionable scheduled results quiet unless periodic\\nreports were requested.\\n\\n## Workflow\\n\\n1. Review signal:\\n\\n```bash\\nfclt ai review reconcile --since --until --json\\nfclt ai writeback group --by asset\\nfclt ai writeback summarize --by domain\\nfclt ai evolve list\\n```\\n\\nFor an enabled scheduled loop, inspect its durable queue and observed scheduler\\nhealth with `fclt ai loop status --json`. Use `fclt ai loop run --dry-run\\n--json` for a fresh incremental scan that does not advance cursors or write\\nreconciliation or loop state. The full queue is authoritative; the notification\\ndelta intentionally suppresses unchanged items.\\n\\n2. Assess proposal readiness before mutating state:\\n\\n```bash\\nfclt ai evolve assess --asset --json\\n```\\n\\nUse the assessment recommendation as the decision checkpoint:\\n\\n- `reconcile_sources`: run the bounded read-only source review; writebacks alone cannot prove the window is empty.\\n- `review_reconciled_signals`: review correlated dispositions and linked work without creating one proposal per ticket.\\n- `no_mutation`: do not change capability state; ask for a target or evidence.\\n- `record_more_writeback`: explain what recurrence would justify evolution and record a new writeback only if there is fresh concrete evidence.\\n- `propose`: create the smallest target-specific draft when review-state mutation is already authorized; otherwise present the proposed action. Retain canonical apply and promotion gates.\\n- `review_existing_proposal`: inspect or revise the existing proposal instead of creating a duplicate.\\n\\n3. Propose only when evidence is strong enough:\\n\\n```bash\\nfclt ai evolve propose\\n```\\n\\n4. Draft and inspect:\\n\\n```bash\\nfclt ai evolve draft EV-00001\\nfclt ai evolve review EV-00001\\n```\\n\\n5. Present the reviewed proposal with its exact target, scope, evidence, and required authority.\\nCanonical apply and cross-scope promotion are unavailable through this plugin. Stop at the\\nreviewed proposal; do not use shell commands to bypass the unavailable surface.\\n\\n6. Verify the outcome after the producing loop has had a real chance to run:\\n\\n```bash\\nfclt ai writeback link WB-00001 --issue TEAM-123\\nfclt ai writeback disposition WB-00001 --type task --target TEAM-123\\nfclt ai evolve verify EV-00001 --effectiveness improved --evidence test:post-apply\\n```\\n\\nApply is not completion. Do not resolve source writebacks until post-apply evidence shows the\\nintended behavior improved. Treat recurrence as unchanged or regressed evidence linked to the same\\nevolution, not as an unrelated singleton.\\n\\n## Proposal Kinds\\n\\n- `update_asset`\\n- `create_asset`\\n- `extract_snippet`\\n- `add_skill`\\n- `promote_asset`\\n\\n## Rules\\n\\n- Prefer the smallest valid proposal kind.\\n- Keep project-specific behavior project-scoped until reuse is proven.\\n- Require approval covering the exact scope before applying global instructions, global skills, plugin behavior, or other broad shared surfaces. Existing approval for that action and scope is sufficient; never bypass an unavailable or restricted apply surface.\\n- Reject or park proposals that are stale, duplicated, vague, or unsupported.\\n- Use the operator\'s task system for executable implementation work that needs owner, priority, or state.\\n- A no-op answer must still be useful: include the evidence grade, missing signal, next writeback target, and exact approval boundary.\\n- State the problem, source evidence, reason for the selected target, risk,\\n expected outcome, verification plan, assumptions, and undo path before any\\n review-producing or mutating action.\\n- Preview and verify the exact scope. Never overwrite authored capability\\n silently or treat generated/rendered output as the canonical target.\\n- Canonical apply and cross-scope promotion are unavailable through the plugin\\n until a transaction-safe API can return tested rollback data. Do not bypass\\n that boundary with shell or arbitrary CLI arguments.\\n- The plugin exposes only closed-schema loop status and preview actions.\\n Scheduler enable/disable/run and external tracker mutation remain outside the\\n MCP surface.\\n\\n## Output\\n\\n- proposals reviewed\\n- repeated signal\\n- assessment recommendation\\n- proposal created or updated\\n- approvals needed\\n- apply/reject/no-op rationale\\n- actual changed records/artifacts, verification result, and recovery route\\n","skills/fclt-setup/SKILL.md":"---\\nname: fclt-setup\\ndescription: Install, update, inspect, and initialize fclt from Codex.\\ntags: [fclt, setup, codex, onboarding]\\n---\\n\\n# fclt-setup\\n\\n## When To Use\\nUse this skill when a user wants Codex to install, update, configure, inspect, or repair fclt.\\n\\nUse it for:\\n\\n- checking whether `fclt` is installed and current\\n- initializing global `~/.ai`\\n- discovering repositories and enrolling a reviewed minimal project layer\\n- installing or refreshing the built-in operating-model pack\\n- checking setup health with `doctor`\\n- finding canonical, generated, runtime, and review paths\\n\\n## Workflow\\n\\n1. For inspection, start with `fclt --version`, `fclt paths --json`, and `fclt doctor --json`. Run setup only when initialization or repair is authorized; existing authorization for the same targets is sufficient. Bootstrap an authorized global setup with:\\n\\n```bash\\nfclt setup\\n```\\n\\nThis initializes or safely updates global capability, prepares writeback/evolution review state,\\nand installs the Codex plugin when Codex is available. It does not initialize the current\\nrepository.\\n\\n2. Inspect runtime selection and compatibility with `fclt_runtime` action\\n `status`. Report the selected executable, version, source, protocol\\n compatibility, and fresh-session state.\\n\\nIf no compatible runtime is available, use the staged lifecycle:\\n\\n- `check` is read-only\\n- `stage` requires an explicit version and approval, but does not activate it\\n- `apply` requires approval plus the staged checksum precondition\\n- `rollback` verifies and restores the retained prior runtime\\n\\nNever curl-pipe code, use an unverified mutable URL, or replace an existing\\nglobal installation silently.\\n\\n3. Check current setup state and exact repair actions:\\n\\n```bash\\nfclt --version\\nfclt paths --json\\nfclt doctor --json\\n```\\n\\nThrough MCP, call `fclt_setup` with an explicit `global` or\\n`global_and_project` scope. Project setup also requires the exact `cwd`.\\nPreview is the default; apply requires both `dryRun: false` and\\n`approve: true`. `global_and_project` returns a no-write project enrollment\\nplan; project application still uses the typed CLI plan-hash contract below.\\n\\n4. For advanced manual recovery, initialize global capability when missing:\\n\\n```bash\\nfclt templates init operating-model --global\\n```\\n\\n5. Discover candidate repositories only beneath explicit roots:\\n\\n```bash\\nfclt projects discover --root ~/dev --since 30d --json\\n```\\n\\nDiscovery is bounded and read-only. Review duplicate clone/worktree groups,\\ndirty state, existing guidance, stable portfolio identity, and the separate\\ncheckout/worktree execution identity before selecting a project. Never\\nbulk-enroll the discovery result.\\n\\n6. Preview the exact minimal project enrollment plan:\\n\\n```bash\\nfclt project init --project-root /path/to/repo --json\\n```\\n\\nThe minimal layer is `.ai/.gitignore` plus `.ai/config.toml`. It does not\\ninstall the operating pack, enable managed rendering, schedule a loop, or copy\\n`AGENTS.md`/`CLAUDE.md`. Review every canonical, generated, and machine-local\\nwrite plus the rollback command.\\n\\nIf project guidance should be adopted, name each canonical file explicitly:\\n\\n```bash\\nfclt project init --project-root /path/to/repo \\\\\\n --guidance AGENTS.md --json\\n```\\n\\nGuidance adoption is reference-only. fclt previews the full content and hash,\\nand refuses untracked, modified, `assume-unchanged`, `skip-worktree`,\\nsecret-shaped, or machine-path-bearing input. Cleanliness requires identical\\nworktree, index, and `HEAD` blobs.\\n\\n7. Apply only the unchanged reviewed plan:\\n\\n```bash\\nfclt project init --project-root /path/to/repo \\\\\\n --apply --plan-sha --json\\n```\\n\\nIf options, source files, or preconditions change, discard the old hash and\\npreview again. Apply serializes portfolio registry mutations, isolates\\nlocation-bearing generated state per checkout/worktree, refuses symlinked\\ngenerated targets, and publishes the receipt only after the registry commit.\\n\\n8. Inspect health, coverage, and lifecycle:\\n\\n```bash\\nfclt projects status --root /path/to/repo --json\\nfclt project disable --project-root /path/to/repo --json\\nfclt project rollback --receipt --json\\n```\\n\\nDisable and remove decisions preserve canonical files, receipts, and review\\nhistory. Rollback previews by default and refuses drift.\\n\\n9. Install the full operating pack only when explicitly requested:\\n\\n```bash\\nfclt templates init operating-model --project --dry-run\\nfclt templates init operating-model --project\\n```\\n\\nThis is distinct from minimal enrollment and does not seed project\\n`AGENTS.global.md` from repository guidance.\\n\\n10. Refresh global pack defaults non-destructively:\\n\\n```bash\\nfclt templates init operating-model --global --update --dry-run\\nfclt templates init operating-model --global --update\\n```\\n\\n11. Use `--force` only when the user explicitly wants to replace local edits.\\n\\n## Rules\\n\\n- Preserve existing `AGENTS.md`, `CLAUDE.md`, and `AGENTS.global.md` guidance.\\n- Never infer guidance adoption from filenames or copy repository guidance into\\n `.ai/AGENTS.global.md`.\\n- Treat root `AGENTS.md` or `CLAUDE.md` as canonical repository guidance unless\\n the user explicitly chooses another tracked, clean source.\\n- Write the protective `.ai/.gitignore` before generated state.\\n- Keep canonical project files separate from machine-local generated indexes,\\n registries, receipts, and scheduling state.\\n- Keep minimal enrollment separate from the full operating pack and managed\\n rendering.\\n- Treat `doctor --json` issues as setup facts, not user-facing blame.\\n- Treat Codex plugin registration as weaker evidence than fresh-session tool discovery.\\n- Treat external trackers as separate integrations. Core readiness depends only on configured local evidence coverage, not a vendor plugin or token.\\n- Prefer temp-root smoke tests for install/update behavior.\\n- Do not enable managed rendering unless the user wants fclt to write tool homes.\\n- Preview before mutation and state the exact global/project/plugin target.\\n- Do not report a staged runtime or installed plugin as active until the active\\n handshake and fresh-session discovery have been verified.\\n\\n## Output\\n\\n- current installed version\\n- setup health\\n- paths that matter\\n- commands run\\n- what changed\\n- problem, evidence, reason, target, risk, and expected outcome\\n- verification performed and its actual result\\n- assumptions and fresh-session state\\n- exact undo or rollback path\\n- what still needs approval\\n\\n## Versioned skill and plugin sources\\n\\nFor registered skill, custom plugin, or MCP artifact updates, use `fclt capability inventory` and a per-entry `plan` before `apply --id ... --expected-plan ...`. Use the registry\'s explicit source, target, and runtime state roots; pass machine/private choices with ordered `--overlay` files. Acquire upstream updates into isolated staging, review the snapshot, and record its revision and `capability digest` before deploying. Do not run embedded installer or auto-update instructions against fclt-owned outputs, and do not use broad legacy sync to repair drift. Preserve divergent files for source selection.\\n\\nNative plugin managers own their installed files. Registered native versions are intended versions until verified with the provider. `enabled: false` stops fclt management but retains existing files; it is not a provider disable or uninstall operation. See the package\'s `docs/capability-registry.md` for the schema, conflict states, and recovery procedure.\\n","skills/fclt-writeback/SKILL.md":"---\\nname: fclt-writeback\\ndescription: Record and review fclt writebacks from real agent work.\\ntags: [fclt, writeback, learning, feedback-loop]\\n---\\n\\n# fclt-writeback\\n\\n## When To Use\\nUse this skill when work reveals durable friction, missing context, weak verification, stale guidance, repeated success, or a capability gap.\\n\\nWriteback is for preserving signal. It is not for every preference or one-off annoyance.\\n\\n## Authorization\\n\\nProceed with recording or drafting when the current request authorizes that action and scope;\\ndo not request the same approval again. For read-only or proposal-only work, return findings or a\\nproposed writeback without changing state. Strong evidence alone does not grant write authority.\\n\\nRecording or drafting does not authorize canonical application, cross-scope promotion, external\\nmessages, tracker changes, publishing, or scheduler activation. Preserve those separate gates and\\nnative authorization. Keep unchanged, non-actionable scheduled results quiet unless periodic\\nreports were requested.\\n\\n## Workflow\\n\\n1. Decide scope:\\n\\n- `project` when the learning depends on a repo, test harness, architecture, or workflow.\\n- `global` when the learning applies across projects or shared tool behavior.\\n\\n2. Choose the smallest target:\\n\\n- instruction\\n- snippet\\n- skill\\n- agent\\n- MCP/tool config\\n- automation\\n\\n3. Record writeback when the target, evidence, and authorization are clear:\\n\\n```bash\\nfclt ai writeback add \\\\\\n --kind missing_context \\\\\\n --category opportunity \\\\\\n --summary \\"Project verification guidance was not discoverable\\" \\\\\\n --details \\"The task had to reconstruct the command from CI configuration\\" \\\\\\n --impact \\"Verification took longer and could have selected the wrong harness\\" \\\\\\n --attempted-workaround \\"Inspected package scripts and CI\\" \\\\\\n --desired-outcome \\"The supported verification command is available at task start\\" \\\\\\n --sensitivity internal \\\\\\n --evidence session: \\\\\\n --asset @project/instructions/TESTING.md\\n```\\n\\n4. Review current signal:\\n\\n```bash\\nfclt ai writeback list\\nfclt ai writeback group --by asset\\nfclt ai writeback summarize --by domain\\nfclt ai loop activity --project\\n```\\n\\n## Rules\\n\\n- Prefer one high-signal writeback over several weak ones.\\n- Include concrete evidence when possible.\\n- Capture concise context, impact, attempted workaround, desired outcome, and\\n sensitivity when they improve review quality.\\n- Never capture hidden chain-of-thought, raw transcripts, unbounded logs,\\n secrets, tokens, or credential-bearing payloads. Reference the smallest\\n redacted external evidence identifier instead.\\n- Do not copy private project detail into global writebacks.\\n- Use task tracking for executable product/tooling work; use writeback for reusable operating-layer learning.\\n- If the same signal repeats and the target is clear, hand off to `fclt-evolution`.\\n- State the observed problem, evidence, target, reason, expected outcome, and\\n assumptions before recording.\\n- Do not capture secrets, private tokens, or raw sensitive payloads as evidence.\\n- For lifecycle mutations, use an explicit scope and expected prior state.\\n Report the journal/review evidence and the available undo transition.\\n\\n## Output\\n\\n- writeback id or no-op rationale\\n- scope\\n- target asset\\n- evidence summary\\n- whether this is ready for evolution\\n- risk class and approval boundary\\n- actual changed records/artifacts\\n- verification result and recovery route\\n\\n\\nVerify the producing workflow before resolving source writebacks; applying a change alone does not prove effectiveness.\\n"}' ) as Record; export const BUILTIN_FCLT_CODEX_PLUGIN_BINARY_FILES = JSON.parse( diff --git a/src/manage.test.ts b/src/manage.test.ts index 40c6d4e..098afaf 100644 --- a/src/manage.test.ts +++ b/src/manage.test.ts @@ -28,6 +28,15 @@ import { } from "./manage"; import { facultMachineStateDir, legacyFacultStateDirForRoot } from "./paths"; +const BUNDLED_PLUGIN_VERSION = ( + JSON.parse( + await readFile( + new URL("../plugins/fclt/.codex-plugin/plugin.json", import.meta.url), + "utf8" + ) + ) as { version: string } +).version; + const DOLLAR = "$"; function placeholder(name: string): string { @@ -156,7 +165,7 @@ async function setupMutatedCodexPlugin(mutation: InstalledPluginMutation) { codexBin, home, installedMutation: mutation, - selectedVersion: "0.1.5", + selectedVersion: BUNDLED_PLUGIN_VERSION, }); return { home, @@ -3118,7 +3127,7 @@ describe("syncManagedTools", () => { await writeCodexPluginStub({ codexBin, home, - selectedVersion: "0.1.5", + selectedVersion: BUNDLED_PLUGIN_VERSION, }); await writeJson(join(home, ".agents", "plugins", "marketplace.json"), { name: "local", @@ -3176,14 +3185,14 @@ describe("syncManagedTools", () => { codexBin, home, listedVersion: "0.1.2", - selectedVersion: "0.1.5", + selectedVersion: BUNDLED_PLUGIN_VERSION, }); const result = await setupCodexPlugin({ homeDir: home, codexBin }); expect(result.codexInstall.status).toBe("failed"); expect(result.codexInstall.stderr).toContain( - "expected fclt@local version 0.1.5 to be installed and enabled" + `expected fclt@local version ${BUNDLED_PLUGIN_VERSION} to be installed and enabled` ); expect(result.codexInstall.verificationCommand).toEqual([ codexBin, @@ -3205,14 +3214,14 @@ describe("syncManagedTools", () => { "cache", "local", "fclt", - "0.1.5", + BUNDLED_PLUGIN_VERSION, "selected.txt" ) ).exists() ).toBe(false); }); - it("upgrades to plugin 0.1.5 while preserving a cached 0.1.2 payload", async () => { + it("upgrades to the bundled plugin while preserving a cached 0.1.2 payload", async () => { const home = await createTempDir(); const oldCache = join( home, @@ -3234,7 +3243,7 @@ describe("syncManagedTools", () => { await writeCodexPluginStub({ codexBin, home, - selectedVersion: "0.1.5", + selectedVersion: BUNDLED_PLUGIN_VERSION, }); const result = await setupCodexPlugin({ homeDir: home, codexBin }); @@ -3245,7 +3254,7 @@ describe("syncManagedTools", () => { "cache", "local", "fclt", - "0.1.5" + BUNDLED_PLUGIN_VERSION ); expect(result.codexInstall.status).toBe("succeeded"); @@ -3253,7 +3262,7 @@ describe("syncManagedTools", () => { (await Bun.file( join(installedPath, ".codex-plugin", "plugin.json") ).json()) as { version: string } - ).toMatchObject({ version: "0.1.5" }); + ).toMatchObject({ version: BUNDLED_PLUGIN_VERSION }); expect( await Bun.file(join(installedPath, "scripts", "fclt-mcp.cjs")).text() ).toContain("audit-read-only-v1"); @@ -3274,7 +3283,7 @@ describe("syncManagedTools", () => { codexBin, home, installedMutation: "symlink", - selectedVersion: "0.1.5", + selectedVersion: BUNDLED_PLUGIN_VERSION, }); const result = await setupCodexPlugin({ homeDir: home, codexBin }); @@ -3312,7 +3321,7 @@ describe("syncManagedTools", () => { codexBin, home, installedMutation: "unreadable-subtree", - selectedVersion: "0.1.5", + selectedVersion: BUNDLED_PLUGIN_VERSION, }); const result = await setupCodexPlugin({ homeDir: home, codexBin }); @@ -3333,7 +3342,7 @@ describe("syncManagedTools", () => { "cache", "local", "fclt", - "0.1.5", + BUNDLED_PLUGIN_VERSION, "unexpected-private" ), 0o700 diff --git a/src/setup.test.ts b/src/setup.test.ts index a56b7dc..075d8cb 100644 --- a/src/setup.test.ts +++ b/src/setup.test.ts @@ -21,6 +21,15 @@ import { import { facultMachineStateDir } from "./paths"; import { bootstrapFclt } from "./setup"; +const BUNDLED_PLUGIN_VERSION = ( + JSON.parse( + await readFile( + new URL("../plugins/fclt/.codex-plugin/plugin.json", import.meta.url), + "utf8" + ) + ) as { version: string } +).version; + const cleanupPaths: string[] = []; const cliEntry = join(import.meta.dir, "index.ts"); @@ -693,7 +702,7 @@ describe("zero-config setup", () => { 'import { dirname, join } from "node:path";', "const argv = process.argv.slice(2);", 'const pluginId = "fclt@hack-local";', - 'const version = "0.1.5";', + `const version = ${JSON.stringify(BUNDLED_PLUGIN_VERSION)};`, 'const installedPath = join(process.env.HOME, ".codex", "plugins", "cache", "hack-local", "fclt", version);', 'mkdirSync(join(process.env.HOME, ".codex"), { recursive: true });', 'writeFileSync(join(process.env.HOME, ".codex", "config.toml"), `[plugins."` + pluginId + `"]\nenabled = true\n`);',