specrails-core 5.2.3 → 5.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/README.md +3 -0
  2. package/bin/specrails-core.mjs +5 -0
  3. package/dist/agent-runtime/capabilities.d.ts +27 -0
  4. package/dist/agent-runtime/capabilities.js +73 -0
  5. package/dist/agent-runtime/capabilities.js.map +1 -0
  6. package/dist/agent-runtime/cli-executor.d.ts +43 -0
  7. package/dist/agent-runtime/cli-executor.js +405 -0
  8. package/dist/agent-runtime/cli-executor.js.map +1 -0
  9. package/dist/agent-runtime/cli-process.d.ts +32 -0
  10. package/dist/agent-runtime/cli-process.js +159 -0
  11. package/dist/agent-runtime/cli-process.js.map +1 -0
  12. package/dist/agent-runtime/cli.d.ts +39 -0
  13. package/dist/agent-runtime/cli.js +216 -0
  14. package/dist/agent-runtime/cli.js.map +1 -0
  15. package/dist/agent-runtime/codex-schema.d.ts +4 -0
  16. package/dist/agent-runtime/codex-schema.js +31 -0
  17. package/dist/agent-runtime/codex-schema.js.map +1 -0
  18. package/dist/agent-runtime/config.d.ts +11 -0
  19. package/dist/agent-runtime/config.js +263 -0
  20. package/dist/agent-runtime/config.js.map +1 -0
  21. package/dist/agent-runtime/core-host.d.ts +41 -0
  22. package/dist/agent-runtime/core-host.js +132 -0
  23. package/dist/agent-runtime/core-host.js.map +1 -0
  24. package/dist/agent-runtime/durable-store.d.ts +22 -0
  25. package/dist/agent-runtime/durable-store.js +205 -0
  26. package/dist/agent-runtime/durable-store.js.map +1 -0
  27. package/dist/agent-runtime/efficiency-summary.d.ts +67 -0
  28. package/dist/agent-runtime/efficiency-summary.js +52 -0
  29. package/dist/agent-runtime/efficiency-summary.js.map +1 -0
  30. package/dist/agent-runtime/efficiency-types.d.ts +60 -0
  31. package/dist/agent-runtime/efficiency-types.js +8 -0
  32. package/dist/agent-runtime/efficiency-types.js.map +1 -0
  33. package/dist/agent-runtime/efficiency.d.ts +5 -0
  34. package/dist/agent-runtime/efficiency.js +35 -0
  35. package/dist/agent-runtime/efficiency.js.map +1 -0
  36. package/dist/agent-runtime/evaluation-corpus.d.ts +13 -0
  37. package/dist/agent-runtime/evaluation-corpus.js +8 -0
  38. package/dist/agent-runtime/evaluation-corpus.js.map +1 -0
  39. package/dist/agent-runtime/evaluation.d.ts +43 -0
  40. package/dist/agent-runtime/evaluation.js +171 -0
  41. package/dist/agent-runtime/evaluation.js.map +1 -0
  42. package/dist/agent-runtime/executor-types.d.ts +125 -0
  43. package/dist/agent-runtime/executor-types.js +29 -0
  44. package/dist/agent-runtime/executor-types.js.map +1 -0
  45. package/dist/agent-runtime/executors.d.ts +23 -0
  46. package/dist/agent-runtime/executors.js +48 -0
  47. package/dist/agent-runtime/executors.js.map +1 -0
  48. package/dist/agent-runtime/gemini-policy.d.ts +8 -0
  49. package/dist/agent-runtime/gemini-policy.js +36 -0
  50. package/dist/agent-runtime/gemini-policy.js.map +1 -0
  51. package/dist/agent-runtime/graph/artifacts.d.ts +26 -0
  52. package/dist/agent-runtime/graph/artifacts.js +205 -0
  53. package/dist/agent-runtime/graph/artifacts.js.map +1 -0
  54. package/dist/agent-runtime/graph/nodes.d.ts +39 -0
  55. package/dist/agent-runtime/graph/nodes.js +321 -0
  56. package/dist/agent-runtime/graph/nodes.js.map +1 -0
  57. package/dist/agent-runtime/graph/review-policy.d.ts +18 -0
  58. package/dist/agent-runtime/graph/review-policy.js +32 -0
  59. package/dist/agent-runtime/graph/review-policy.js.map +1 -0
  60. package/dist/agent-runtime/graph/roles.d.ts +47 -0
  61. package/dist/agent-runtime/graph/roles.js +190 -0
  62. package/dist/agent-runtime/graph/roles.js.map +1 -0
  63. package/dist/agent-runtime/graph/state.d.ts +83 -0
  64. package/dist/agent-runtime/graph/state.js +23 -0
  65. package/dist/agent-runtime/graph/state.js.map +1 -0
  66. package/dist/agent-runtime/graph-checkpointer.d.ts +61 -0
  67. package/dist/agent-runtime/graph-checkpointer.js +137 -0
  68. package/dist/agent-runtime/graph-checkpointer.js.map +1 -0
  69. package/dist/agent-runtime/index.d.ts +20 -0
  70. package/dist/agent-runtime/index.js +21 -0
  71. package/dist/agent-runtime/index.js.map +1 -0
  72. package/dist/agent-runtime/kimi-acp.d.ts +11 -0
  73. package/dist/agent-runtime/kimi-acp.js +190 -0
  74. package/dist/agent-runtime/kimi-acp.js.map +1 -0
  75. package/dist/agent-runtime/openai-executor.d.ts +26 -0
  76. package/dist/agent-runtime/openai-executor.js +192 -0
  77. package/dist/agent-runtime/openai-executor.js.map +1 -0
  78. package/dist/agent-runtime/openspec-tool-server.d.ts +1 -0
  79. package/dist/agent-runtime/openspec-tool-server.js +37 -0
  80. package/dist/agent-runtime/openspec-tool-server.js.map +1 -0
  81. package/dist/agent-runtime/openspec.d.ts +123 -0
  82. package/dist/agent-runtime/openspec.js +263 -0
  83. package/dist/agent-runtime/openspec.js.map +1 -0
  84. package/dist/agent-runtime/prompts.d.ts +44 -0
  85. package/dist/agent-runtime/prompts.js +310 -0
  86. package/dist/agent-runtime/prompts.js.map +1 -0
  87. package/dist/agent-runtime/provider-diagnostic.d.ts +2 -0
  88. package/dist/agent-runtime/provider-diagnostic.js +25 -0
  89. package/dist/agent-runtime/provider-diagnostic.js.map +1 -0
  90. package/dist/agent-runtime/repository-context.d.ts +25 -0
  91. package/dist/agent-runtime/repository-context.js +100 -0
  92. package/dist/agent-runtime/repository-context.js.map +1 -0
  93. package/dist/agent-runtime/review-context.d.ts +14 -0
  94. package/dist/agent-runtime/review-context.js +41 -0
  95. package/dist/agent-runtime/review-context.js.map +1 -0
  96. package/dist/agent-runtime/role-routing.d.ts +10 -0
  97. package/dist/agent-runtime/role-routing.js +29 -0
  98. package/dist/agent-runtime/role-routing.js.map +1 -0
  99. package/dist/agent-runtime/role-state.d.ts +19 -0
  100. package/dist/agent-runtime/role-state.js +24 -0
  101. package/dist/agent-runtime/role-state.js.map +1 -0
  102. package/dist/agent-runtime/runtime-identity.d.ts +11 -0
  103. package/dist/agent-runtime/runtime-identity.js +33 -0
  104. package/dist/agent-runtime/runtime-identity.js.map +1 -0
  105. package/dist/agent-runtime/tool-event.d.ts +3 -0
  106. package/dist/agent-runtime/tool-event.js +24 -0
  107. package/dist/agent-runtime/tool-event.js.map +1 -0
  108. package/dist/agent-runtime/verification-plan.d.ts +55 -0
  109. package/dist/agent-runtime/verification-plan.js +206 -0
  110. package/dist/agent-runtime/verification-plan.js.map +1 -0
  111. package/dist/agent-runtime/workflow-types.d.ts +226 -0
  112. package/dist/agent-runtime/workflow-types.js +2 -0
  113. package/dist/agent-runtime/workflow-types.js.map +1 -0
  114. package/dist/agent-runtime/workflow.d.ts +17 -0
  115. package/dist/agent-runtime/workflow.js +636 -0
  116. package/dist/agent-runtime/workflow.js.map +1 -0
  117. package/dist/agent-runtime/workspace-tools.d.ts +29 -0
  118. package/dist/agent-runtime/workspace-tools.js +282 -0
  119. package/dist/agent-runtime/workspace-tools.js.map +1 -0
  120. package/dist/installer/cli.d.ts +35 -0
  121. package/dist/installer/cli.js +3 -0
  122. package/dist/installer/cli.js.map +1 -1
  123. package/dist/installer/commands/doctor.d.ts +27 -0
  124. package/dist/installer/commands/framework.d.ts +85 -0
  125. package/dist/installer/commands/init.d.ts +147 -0
  126. package/dist/installer/commands/update.d.ts +56 -0
  127. package/dist/installer/commands/v5-migration.d.ts +32 -0
  128. package/dist/installer/phases/framework-lifecycle.d.ts +53 -0
  129. package/dist/installer/phases/install-config.d.ts +64 -0
  130. package/dist/installer/phases/manifest.d.ts +45 -0
  131. package/dist/installer/phases/prereqs.d.ts +51 -0
  132. package/dist/installer/phases/provider-detect.d.ts +89 -0
  133. package/dist/installer/phases/scaffold.d.ts +211 -0
  134. package/dist/installer/phases/scaffold.js +39 -109
  135. package/dist/installer/phases/scaffold.js.map +1 -1
  136. package/dist/installer/runtime/kimi.d.ts +84 -0
  137. package/dist/installer/runtime/pipeline-state.d.ts +351 -0
  138. package/dist/installer/runtime/pipeline-state.js +514 -41
  139. package/dist/installer/runtime/pipeline-state.js.map +1 -1
  140. package/dist/installer/util/errors.d.ts +46 -0
  141. package/dist/installer/util/exec.d.ts +41 -0
  142. package/dist/installer/util/fs.d.ts +153 -0
  143. package/dist/installer/util/git.d.ts +44 -0
  144. package/dist/installer/util/install-transaction.d.ts +29 -0
  145. package/dist/installer/util/logger.d.ts +31 -0
  146. package/dist/installer/util/paths.d.ts +34 -0
  147. package/dist/installer/util/prompts.d.ts +23 -0
  148. package/dist/installer/util/registry.d.ts +174 -0
  149. package/dist/installer/util/template.d.ts +23 -0
  150. package/docs/README.md +1 -0
  151. package/docs/agent-runtime-efficiency.md +65 -0
  152. package/docs/agent-runtime.md +339 -0
  153. package/integration-contract.json +80 -7
  154. package/package.json +20 -2
  155. package/schemas/agent-runtime.schema.json +77 -0
  156. package/schemas/fixtures/runtime-efficiency-summary.v1.json +790 -0
  157. package/templates/codex-skills/batch-implement/SKILL.md +33 -58
  158. package/templates/codex-skills/implement/SKILL.md +21 -124
  159. package/templates/codex-skills/retry/SKILL.md +8 -34
  160. package/templates/commands/specrails/batch-implement.md +21 -16
  161. package/templates/commands/specrails/implement.md +17 -276
  162. package/templates/commands/specrails/retry.md +6 -34
  163. package/templates/gemini-commands/batch-implement.toml +34 -28
  164. package/templates/gemini-commands/implement.toml +34 -55
  165. package/templates/gemini-commands/retry.toml +10 -16
@@ -0,0 +1,29 @@
1
+ export declare const frameworkLifecycleLockPath: (frameworkDir: string) => string;
2
+ export declare function withFrameworkLifecycleLock<T>(frameworkDir: string, apply: () => T | Promise<T>): Promise<T>;
3
+ /**
4
+ * A surface the transaction protects.
5
+ *
6
+ * `snapshotContents: false` marks a surface whose CONTENTS must not be copied:
7
+ * the versioned framework store, which `installFramework` already rebuilds from
8
+ * a content-hash stamp and stages through a temp directory. Rollback still
9
+ * removes such a surface when the install CREATED it, and leaves a pre-existing
10
+ * one exactly as it stands — copying it bought no safety and cost a full copy of
11
+ * the framework store on every install.
12
+ */
13
+ export type ProtectedSurface = string | {
14
+ path: string;
15
+ snapshotContents: boolean;
16
+ };
17
+ /** Snapshot only installer-owned surfaces; never follow links into a shared framework.
18
+ * Backups survive a failed rollback and their location is reported for recovery.
19
+ *
20
+ * Snapshotting requires NO filesystem privilege: links are recorded by target
21
+ * rather than recreated (see `snapshotTree`), so a protected surface that is —
22
+ * or contains — a Windows junction no longer fails the install on an account
23
+ * without SeCreateSymbolicLinkPrivilege.
24
+ */
25
+ export declare function withInstallRollback<T>(paths: ProtectedSurface[], apply: () => Promise<T>): Promise<T>;
26
+ export declare function compareCoreVersions(left: string, right: string): number | null;
27
+ /** Refuse an implicit downgrade. Explicit swap-current is the rollback interface. */
28
+ export declare function assertNoCoreDowngrade(current: string | null, target: string): void;
29
+ export declare function currentFrameworkVersion(frameworkDir: string): string | null;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Coloured output helpers for the installer. The emoji prefixes match
3
+ * what the retired bash scripts printed (`✓ ⚠ ✗ →`) so existing user
4
+ * bug reports and screenshots still read the same.
5
+ *
6
+ * The logger writes to stdout by default; callers may override via
7
+ * {@link setLoggerStreams} (useful in tests that capture output).
8
+ */
9
+ interface Streams {
10
+ out: NodeJS.WritableStream;
11
+ err: NodeJS.WritableStream;
12
+ }
13
+ export declare function setLoggerStreams(next: Partial<Streams>): void;
14
+ /** Restore logger to process.stdout/stderr. */
15
+ export declare function resetLoggerStreams(): void;
16
+ /** Section heading — bold, no prefix, leading blank line. */
17
+ export declare function step(title: string): void;
18
+ /** Success line. Prefix: ` ✓ ` in green. */
19
+ export declare function ok(msg: string): void;
20
+ /** Warning line. Prefix: ` ⚠ ` in yellow. */
21
+ export declare function warn(msg: string): void;
22
+ /** Failure line, routed to stderr. Prefix: ` ✗ ` in red. */
23
+ export declare function fail(msg: string): void;
24
+ /** Info line. Prefix: ` → ` in blue. */
25
+ export declare function info(msg: string): void;
26
+ /** Print a fatal error and its hint, routed to stderr. */
27
+ export declare function fatal(message: string, hint?: string): void;
28
+ /** Primitive write for cases where the caller owns formatting. */
29
+ export declare function rawOut(text: string): void;
30
+ export declare function rawErr(text: string): void;
31
+ export {};
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Paths the installer MUST NOT create, modify, or delete. These hold
3
+ * user / team state that survives re-runs (profile JSON authored by
4
+ * specrails-desktop, custom agents authored by the user). Breaking this
5
+ * contract silently destroys user work.
6
+ *
7
+ * Audited by vitest spec `reserved-paths.test.ts`.
8
+ */
9
+ export declare const RESERVED_PATHS: readonly [".specrails/profiles/", ".claude/agents/custom-", ".kimi-code/skills/custom-", ".kimi-code/skills/rails/custom-"];
10
+ /**
11
+ * Returns true when a repo-relative path falls inside a reserved
12
+ * region. Accepts both POSIX (/) and Windows (\) separators; the
13
+ * check normalises internally so callers can pass a value straight
14
+ * from `path.relative()` without worrying about host platform.
15
+ */
16
+ export declare function isReservedPath(relPath: string): boolean;
17
+ /**
18
+ * Joins segments and forces POSIX separators. Use when a generated
19
+ * string is destined for a cross-platform artefact (JSON manifest,
20
+ * YAML config) rather than the local filesystem.
21
+ */
22
+ export declare function toPosix(...segments: string[]): string;
23
+ /**
24
+ * Joins segments using the host platform's separator. Use when the
25
+ * result goes directly to `fs.*` / `child_process.*` calls.
26
+ */
27
+ export declare function toNative(...segments: string[]): string;
28
+ /**
29
+ * Repository-relative representation of an absolute path. Returns a
30
+ * POSIX-style path so callers can store it in manifests without OS
31
+ * contamination. If `absPath` is outside `repoRoot`, returns the
32
+ * original absolute path unchanged (POSIX-normalised).
33
+ */
34
+ export declare function repoRelative(repoRoot: string, absPath: string): string;
@@ -0,0 +1,23 @@
1
+ export interface TextOptions {
2
+ message: string;
3
+ /** Value used when stdin is non-TTY. Absence here in non-TTY mode throws. */
4
+ default?: string;
5
+ /** Returns error message when invalid, or `true` / undefined when valid. */
6
+ validate?: (value: string) => string | true | undefined;
7
+ }
8
+ export declare function text(opts: TextOptions): Promise<string>;
9
+ export interface ConfirmOptions {
10
+ message: string;
11
+ default?: boolean;
12
+ }
13
+ export declare function confirmYesNo(opts: ConfirmOptions): Promise<boolean>;
14
+ export interface SelectOptions<T> {
15
+ message: string;
16
+ choices: Array<{
17
+ name: string;
18
+ value: T;
19
+ description?: string;
20
+ }>;
21
+ default?: T;
22
+ }
23
+ export declare function chooseOne<T>(opts: SelectOptions<T>): Promise<T>;
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Shared artifact registry — the contract that lets specrails-core place,
3
+ * and specrails-desktop read, a repo's relocated artifacts OUTSIDE the repo,
4
+ * under `$HOME/.specrails/projects/<slug>/workspace`.
5
+ *
6
+ * Single source of truth: `$HOME/.specrails/registry.json`, an inspectable,
7
+ * schema-versioned JSON file keyed by the **canonical realpath of the repo**.
8
+ * specrails-desktop is the primary writer (a projection of its `desktop.sqlite`);
9
+ * specrails-core reads it and, when run standalone, allocates its own entry.
10
+ *
11
+ * Design contract: `docs/internals/global-artifacts-alignment-contract.md`
12
+ * (in the specrails-desktop repo). The slug algorithm, canonical-key rule,
13
+ * atomic-write rule and lock protocol here MUST stay byte-identical to the
14
+ * desktop-side implementation — the correctness of the cross-tool contract
15
+ * depends on both tools resolving the same repo to the same paths.
16
+ *
17
+ * Everything in this module is synchronous to fit the installer's sync flow.
18
+ */
19
+ /** Registry schema version. A reader that sees a higher value MUST treat all
20
+ * entries as absent (legacy fallback), never mis-parse. */
21
+ export declare const REGISTRY_SCHEMA_VERSION = 1;
22
+ /** Who owns/allocated an entry. Encodes the single-writer-at-a-time rule. */
23
+ export type RegistrySource = 'desktop' | 'core-standalone';
24
+ /** One repo's relocated-artifact locations. All paths are absolute and in the
25
+ * host platform's native separator; consumers treat them as opaque. */
26
+ export interface ProjectEntry {
27
+ /** The canonical realpath of the repo (mirror of the map key). */
28
+ repoPath: string;
29
+ /** Shared slug — MUST equal desktop.sqlite `projects.slug` for the same repo. */
30
+ slug: string;
31
+ /** `$HOME/.specrails/projects/<slug>/workspace` — root of all relocated artifacts. */
32
+ workspaceDir: string;
33
+ /** The dir core treats as its `.specrails`/install root instead of repoRoot (= workspaceDir). */
34
+ artifactRoot: string;
35
+ /** Always the repo (= repoPath); carries the `openspec/**` + worktree carve-outs. */
36
+ codeRoot: string;
37
+ /** Runtime-state base (agent-memory, pipeline-state, …). Injected as SPECRAILS_STATE_DIR. */
38
+ stateDir: string;
39
+ /** Absolute path to the relocated local-tickets.json. Injected as SPECRAILS_TICKETS_PATH. */
40
+ ticketsPath: string;
41
+ /** Absolute path to backlog-config.json (Jira read-only switch). Injected as SPECRAILS_BACKLOG_CONFIG_PATH. */
42
+ backlogConfigPath: string;
43
+ /** Relocated `.specrails/profiles/`. Injected as SPECRAILS_PROFILES_DIR. */
44
+ profilesDir: string;
45
+ /** Relocated `.specrails/plugins/` (desktop-only; present for inspectability). */
46
+ pluginsStateDir: string;
47
+ /** Relocated `.specrails/file-summaries/` (desktop-only). */
48
+ fileSummariesDir: string;
49
+ /** Installed providers; mirror of desktop.sqlite `projects.providers`. */
50
+ providers: string[];
51
+ /** providers[0]; mirror of desktop.sqlite `projects.provider`. */
52
+ primaryProvider: string;
53
+ /** The `specrails-version` pin (name/format frozen; only location moved). */
54
+ coreVersion?: string;
55
+ createdAt?: string;
56
+ lastInstallAt?: string;
57
+ /** Single-owner-at-a-time marker; governs reconciliation. */
58
+ source: RegistrySource;
59
+ /** desktop.sqlite projects.id when source='desktop', for robust repo-move re-link. */
60
+ desktopProjectId?: string;
61
+ }
62
+ /** On-disk shape of `registry.json`. */
63
+ export interface RegistryFile {
64
+ schemaVersion: number;
65
+ generator?: string;
66
+ updatedAt?: string;
67
+ /** Map: canonical-repo-key -> ProjectEntry. */
68
+ projects: Record<string, ProjectEntry>;
69
+ }
70
+ /** The flattened result both tools consume. */
71
+ export interface Resolution {
72
+ /** Normalized map key. */
73
+ key: string;
74
+ repoPath: string;
75
+ slug: string;
76
+ workspaceDir: string;
77
+ artifactRoot: string;
78
+ codeRoot: string;
79
+ stateDir: string;
80
+ ticketsPath: string;
81
+ backlogConfigPath: string;
82
+ profilesDir: string;
83
+ pluginsStateDir: string;
84
+ fileSummariesDir: string;
85
+ providers: string[];
86
+ primaryProvider: string;
87
+ source: RegistrySource | 'legacy';
88
+ /** True when there is no registry entry and we fell back to the in-repo layout. */
89
+ isLegacy: boolean;
90
+ }
91
+ export interface ResolveOptions {
92
+ /** When true, allocate + persist an entry if none exists. Readers pass false. */
93
+ allocate?: boolean;
94
+ /** Who is allocating (only consulted when allocate=true). Default 'core-standalone'. */
95
+ allocator?: RegistrySource;
96
+ /** Override `$HOME` (tests). Default `os.homedir()`. */
97
+ home?: string;
98
+ /** Providers to record on allocation. Default ['claude']. */
99
+ providers?: string[];
100
+ coreVersion?: string;
101
+ desktopProjectId?: string;
102
+ /** Override timestamp (tests). */
103
+ now?: string;
104
+ }
105
+ /**
106
+ * Slug derivation — byte-identical to specrails-desktop's `slugify`
107
+ * (`server/desktop-router.ts`). Do not "improve" it; parity is the contract.
108
+ */
109
+ export declare function slugify(name: string): string;
110
+ /** `$HOME` for the registry, overridable for tests. */
111
+ export declare function resolveHome(home?: string): string;
112
+ /** Absolute path to `registry.json`. */
113
+ export declare function registryPath(home?: string): string;
114
+ /**
115
+ * Absolute path to the versioned framework store
116
+ * (`<home>/.specrails/framework`). The bundled-framework split materializes the
117
+ * provider-invariant subtree to `<frameworkDir>/<version>/<providerDir>/` once
118
+ * and every workspace symlinks `<frameworkDir>/current/<providerDir>/...`. Shares
119
+ * the SAME home as the registry so framework, registry, and workspaces co-locate
120
+ * (and tests pinning `SPECRAILS_REGISTRY_HOME` redirect all three together).
121
+ */
122
+ export declare function frameworkRoot(home?: string): string;
123
+ /** Absolute path to the advisory lock file. */
124
+ export declare function lockPath(home?: string): string;
125
+ /** `fs.realpathSync` that falls back to the resolved-but-unreal path on error
126
+ * (the path may not exist yet, or be on a volume that rejects realpath). */
127
+ export declare function realpathSafe(abs: string): string;
128
+ /** Case-fold the key on case-insensitive platforms (macOS, Windows) so two
129
+ * spellings of the same path map to one entry. The stored `repoPath` keeps
130
+ * its canonical case; only the index key is folded. */
131
+ export declare function normalizeKey(canon: string): string;
132
+ /** Canonical repo path: resolve to absolute, then realpath (collapses symlinks). */
133
+ export declare function canonicalizeRepoPath(repoPathInput: string): string;
134
+ /** The per-project sub-path layout under a workspace dir. Single source of the
135
+ * layout so writer and (allocation) reader never disagree. */
136
+ export declare function workspaceLayout(home: string, slug: string, canon: string, primaryProvider?: string): Omit<ProjectEntry, 'providers' | 'primaryProvider' | 'coreVersion' | 'createdAt' | 'lastInstallAt' | 'source' | 'desktopProjectId'>;
137
+ /** The in-repo layout, used when there is no registry entry (legacy fallback). */
138
+ export declare function legacyResolution(canon: string): Resolution;
139
+ /** Flatten a stored entry into a Resolution. */
140
+ export declare function entryToResolution(key: string, entry: ProjectEntry): Resolution;
141
+ /**
142
+ * Total, fail-open read. A missing file, a parse error, or a `schemaVersion`
143
+ * greater than we understand all yield an empty registry, so a caller treats
144
+ * it as "no entry" and (if allocating) writes a fresh, understood entry rather
145
+ * than crashing. Biases toward availability over strict consistency — correct
146
+ * for a local, inspectable file.
147
+ */
148
+ export declare function readRegistryOrEmpty(home?: string): RegistryFile;
149
+ /** Write `data` to `filePath` atomically: temp file in the same dir, fsync,
150
+ * rename. A reader (even without the lock) only ever sees a complete old or
151
+ * new file — the lock serialises writers, the rename protects readers. */
152
+ export declare function atomicWrite(filePath: string, data: string): void;
153
+ /**
154
+ * Run `fn` while holding an advisory lock over the registry. Mutual exclusion
155
+ * is an exclusive-create lock file (`open(..., 'wx')`) with bounded spin-retry
156
+ * and stale-lock breaking (a lock whose mtime exceeds the TTL is reclaimed —
157
+ * covers a crashed writer). Read-only callers never take the lock.
158
+ */
159
+ export declare function withFileLock<T>(home: string | undefined, fn: () => T): T;
160
+ /** Deterministic slug allocation: basename-derived, `-N` dedup suffix. Both
161
+ * tools implement this identically so they only ever diverge on the suffix,
162
+ * which read-before-allocate resolves. */
163
+ export declare function allocateSlug(canon: string, existing: ReadonlySet<string>): string;
164
+ /**
165
+ * The shared resolver both tools run. Given a repo path, returns where that
166
+ * repo's artifacts live. With `allocate:true`, creates + persists an entry when
167
+ * none exists (under the lock, with a re-read double-check); readers pass
168
+ * `allocate:false` and fall back to the in-repo legacy layout when absent.
169
+ */
170
+ export declare function resolveArtifacts(repoPathInput: string, opts?: ResolveOptions): Resolution;
171
+ /** Commit version metadata only after successful materialization and assembly.
172
+ * Desktop-owned entries remain the host's responsibility; never claim ownership.
173
+ */
174
+ export declare function recordSuccessfulInstall(repoPath: string, opts: Pick<ResolveOptions, 'home' | 'providers' | 'coreVersion' | 'now'>): void;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Small in-house template renderer. Replaces the bash heredocs the
3
+ * retired install.sh / update.sh used to emit manifests and config
4
+ * files.
5
+ *
6
+ * Syntax (intentionally minimal):
7
+ * ${VAR_NAME} — value interpolation
8
+ * {{#if FLAG}}block{{/if}} — conditional block, rendered when
9
+ * FLAG is truthy in the context
10
+ * {{#ifnot FLAG}}block{{/ifnot}} — conditional block when FLAG is falsy
11
+ *
12
+ * Nested blocks are NOT supported. If a template outgrows this
13
+ * capability we switch to mustache — but every heredoc we've audited
14
+ * fits inside these three forms.
15
+ */
16
+ export type TemplateContext = Record<string, string | boolean | number | null | undefined>;
17
+ /**
18
+ * Renders `template` by evaluating its directives against `context`.
19
+ * Missing interpolation variables render as the empty string rather
20
+ * than throwing — the bash scripts behaved the same way (`${X:-}` in
21
+ * a heredoc). Unknown flags in an `{{#if}}` are treated as falsy.
22
+ */
23
+ export declare function render(template: string, context: TemplateContext): string;
package/docs/README.md CHANGED
@@ -18,6 +18,7 @@ Welcome to the SpecRails docs. This guide will take you from zero to a fully aut
18
18
  | [Plugin Architecture](plugin-architecture.md) | Plugin vs scaffold, what lives where, how updates work |
19
19
  | [Agents](agents.md) | Every agent explained — role, when it runs, why it exists |
20
20
  | [Workflows & Commands](workflows.md) | How to use `/specrails:implement`, `/specrails:get-backlog-specs`, and more |
21
+ | [Programmatic Agent Runtime](agent-runtime.md) | LangGraph phases, Claude/Codex/Gemini/Kimi and local providers, CLI/API configuration, recovery and host delivery |
21
22
  | [Customization](customization.md) | Adapt agents, rules, personas, and conventions to your project |
22
23
  | [Updating](updating.md) | Keep SpecRails up to date without losing your customizations |
23
24
 
@@ -0,0 +1,65 @@
1
+ # Efficient programmatic implementation
2
+
3
+ New runs use workflow 5 and instructions 7. Architect, developer and reviewer keep their official OpenSpec workflow: fast-forward, apply and verify. Verification remains deterministic Core execution, not a fourth AI agent. Custom role text does not replace the mandatory scope, response or OpenSpec protocol.
4
+
5
+ ## Configuration
6
+
7
+ Optional schema-version-1 settings:
8
+
9
+ ```json
10
+ {
11
+ "efficiency": {
12
+ "schemaVersion": 1,
13
+ "contextMode": "incremental",
14
+ "reviewMode": "incremental",
15
+ "planning": "proportional",
16
+ "acceptDeveloperChecks": true,
17
+ "verification": { "maxConcurrency": 1 }
18
+ }
19
+ }
20
+ ```
21
+
22
+ These defaults are normalized once at admission and frozen. `full` disables the corresponding adaptive policy. Incremental context requires a compatible session and a transport that explicitly guarantees continuation. Built-in CLI transports currently report continuation as unknown; stateless API and Kimi transports cannot receive a partial packet. They receive full context. A smaller prompt in the offline continuation fixture does **not** imply the same saving on every provider.
23
+
24
+ Each role optionally accepts `effort` and one `escalation: { model, effort? }` within the same provider. Query actual installed support with `specrails-core runtime capabilities --config config.json`; this never runs inference. Unknown effort support rejects an explicit value at admission. Omitting effort leaves the provider default. No economic tier is selected automatically.
25
+
26
+ Architect escalation is limited to the existing deepen pass. Developer escalation can begin on the third permitted candidate, after two failed outcomes; it adds no attempt to the configured limit. Reviewer escalation repairs malformed responses only, never a valid code rejection. Selected tiers and role session identities persist across interruption. All calls, including repairs, consume the existing budgets.
27
+
28
+ ## Checks and evidence
29
+
30
+ Host and architect checks form an immutable baseline. Developers can add up to 20 checks per response, with at most 100 effective checks. Reusing a developer key replaces that addition's active revision; omitted additions remain required. A harness contains at most eight UTF-8 source files, 64 KiB each and 256 KiB per proposal. Sources live outside delivery under the run's state directory, not in the product repository. The runtime expands a structured command and exports `SPECRAILS_CHECK_REPO_ROOT` for locating repository files.
31
+
32
+ Receipts, acceptance and archive approval bind the candidate and complete verification plan. Source changes, interrupted checks or a newer failure cannot reuse an older green result. A nonterminal continuation rechecks and asks the reviewer to certify the current acceptance criteria; terminal reads do not execute checks. Previously granted archive consent only survives when the candidate, plan and acceptance decisions remain identical.
33
+
34
+ Checks default to `policy.reuse: "never"`. Host-only `snapshot-local` reuse requires explicit deterministic/read-only declarations, complete input/toolchain/dependency contents and a proven executable identity. Ignored installed dependencies must be declared; an unchanged lockfile is insufficient. Unknown inputs rerun. Concurrency defaults to one, with a maximum of four. Only contiguous checks in the same host-declared independence group, on distinct repositories with explicit nonoverlapping resources, run together. Missing resources mean unknown; an explicit empty array means no shared resources.
35
+
36
+ Read recorded evidence without invoking agents:
37
+
38
+ ```sh
39
+ specrails-core runtime evidence --context desktop-context.json --limit 25
40
+ specrails-core runtime evidence --context desktop-context.json --id EVIDENCE_ID --section stdout
41
+ specrails-core runtime evidence --context desktop-context.json --id EVIDENCE_ID --section source --source-id SOURCE_ID
42
+ ```
43
+
44
+ List responses discover opaque evidence/source IDs and bound pagination cursors. Output pages are bounded; stdout/stderr captures retain at most 1 MiB each, with redaction, hashes and truncation markers. Historical source snapshots remain readable after worktree removal. They describe that execution, not current validity. The same scoped resolver is available to developer/reviewer through API and MCP tools.
45
+
46
+ ## Reproducible evaluation
47
+
48
+ Build first, then run the actual offline command:
49
+
50
+ ```sh
51
+ npm run build
52
+ node bin/specrails-core.mjs runtime evaluate --output /tmp/specrails-efficiency-evaluation
53
+ ```
54
+
55
+ Five fixed cases cover static Tetris-like logic, a local feature, a cross-repository contract, verification correction and review correction. Each independent oracle must accept its reference implementation and reject deliberately defective variants. Full and optimized modes use fresh repositories/sessions and identical acceptance gates. The fixed long-context correction must shrink at least 40% without extra invocations. Reports record task, oracle, repository, configuration and runtime identities, failures, sample variation and independent acceptance.
56
+
57
+ Offline tokens and zero fixture cost are synthetic. The initial offline run accepted 5/5 cases in both modes and reduced its fixed correction prompt from 4,774 to 1,777 bytes (62.8%). It did not demonstrate lower monetary cost or faster real-provider execution. Repeat after implementation changes; the report records the tested package identity.
58
+
59
+ Real evaluation is opt-in only: `runtime evaluate --real --config EXPLICIT_MODELS.json --max-cost-usd BUDGET --output OUTPUT`. Select all models and authorize the aggregate spend first. Unsupported spend limits prevent launch; incomplete billing stops further calls. The report distinguishes same-model from configured routing experiments and evaluates the descriptive target of 20% lower aggregate cost per independently accepted output. Small samples and uncontrolled provider caches do not establish universal quality or savings.
60
+
61
+ ## Compatibility and rollback
62
+
63
+ A v4 checkpoint requires its original runtime. Never rewrite checksums or substitute a newly installed package. Desktop retains an immutable package and dependency closure per admitted run. Restoring that proven package is the recovery path; an unknown original package cannot be inferred from matching workflow inputs alone. New runtime configuration or package changes affect new runs only.
64
+
65
+ This source tree is a development package. Publishing Core and updating Desktop's exact version/integrity lock are separate coordinated release steps; local assembly is not proof that a new version has been published.