@skillstate/core 0.0.1 → 2.0.1

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 (88) hide show
  1. package/dist/atomic-write.d.ts +40 -0
  2. package/dist/atomic-write.d.ts.map +1 -0
  3. package/dist/atomic-write.js +97 -0
  4. package/dist/atomic-write.js.map +1 -0
  5. package/dist/clock.d.ts +27 -0
  6. package/dist/clock.d.ts.map +1 -0
  7. package/dist/clock.js +45 -0
  8. package/dist/clock.js.map +1 -0
  9. package/dist/config.d.ts +43 -0
  10. package/dist/config.d.ts.map +1 -0
  11. package/dist/config.js +152 -0
  12. package/dist/config.js.map +1 -0
  13. package/dist/events.d.ts +59 -0
  14. package/dist/events.d.ts.map +1 -0
  15. package/dist/events.js +36 -0
  16. package/dist/events.js.map +1 -0
  17. package/dist/index.d.ts +20 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +28 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/instrumentation.d.ts +35 -0
  22. package/dist/instrumentation.d.ts.map +1 -0
  23. package/dist/instrumentation.js +41 -0
  24. package/dist/instrumentation.js.map +1 -0
  25. package/dist/logger.d.ts +34 -0
  26. package/dist/logger.d.ts.map +1 -0
  27. package/dist/logger.js +40 -0
  28. package/dist/logger.js.map +1 -0
  29. package/dist/migrations.d.ts +35 -0
  30. package/dist/migrations.d.ts.map +1 -0
  31. package/dist/migrations.js +26 -0
  32. package/dist/migrations.js.map +1 -0
  33. package/dist/prompt-transformer.d.ts +106 -0
  34. package/dist/prompt-transformer.d.ts.map +1 -0
  35. package/dist/prompt-transformer.js +265 -0
  36. package/dist/prompt-transformer.js.map +1 -0
  37. package/dist/provider.d.ts +62 -0
  38. package/dist/provider.d.ts.map +1 -0
  39. package/dist/provider.js +46 -0
  40. package/dist/provider.js.map +1 -0
  41. package/dist/redaction.d.ts +26 -0
  42. package/dist/redaction.d.ts.map +1 -0
  43. package/dist/redaction.js +38 -0
  44. package/dist/redaction.js.map +1 -0
  45. package/dist/resilience.d.ts +83 -0
  46. package/dist/resilience.d.ts.map +1 -0
  47. package/dist/resilience.js +171 -0
  48. package/dist/resilience.js.map +1 -0
  49. package/dist/runtime.d.ts +223 -0
  50. package/dist/runtime.d.ts.map +1 -0
  51. package/dist/runtime.js +362 -0
  52. package/dist/runtime.js.map +1 -0
  53. package/dist/schemas/index.d.ts +2 -0
  54. package/dist/schemas/index.d.ts.map +1 -0
  55. package/dist/schemas/index.js +3 -0
  56. package/dist/schemas/index.js.map +1 -0
  57. package/dist/schemas/intercode-ctf.d.ts +3 -0
  58. package/dist/schemas/intercode-ctf.d.ts.map +1 -0
  59. package/dist/schemas/intercode-ctf.js +53 -0
  60. package/dist/schemas/intercode-ctf.js.map +1 -0
  61. package/dist/shutdown.d.ts +20 -0
  62. package/dist/shutdown.d.ts.map +1 -0
  63. package/dist/shutdown.js +42 -0
  64. package/dist/shutdown.js.map +1 -0
  65. package/dist/state-manager.d.ts +23 -0
  66. package/dist/state-manager.d.ts.map +1 -0
  67. package/dist/state-manager.js +129 -0
  68. package/dist/state-manager.js.map +1 -0
  69. package/dist/state-store.d.ts +48 -0
  70. package/dist/state-store.d.ts.map +1 -0
  71. package/dist/state-store.js +101 -0
  72. package/dist/state-store.js.map +1 -0
  73. package/dist/token-tracker.d.ts +104 -0
  74. package/dist/token-tracker.d.ts.map +1 -0
  75. package/dist/token-tracker.js +204 -0
  76. package/dist/token-tracker.js.map +1 -0
  77. package/dist/types.d.ts +61 -0
  78. package/dist/types.d.ts.map +1 -0
  79. package/dist/types.js +2 -0
  80. package/dist/types.js.map +1 -0
  81. package/dist/validate.d.ts +39 -0
  82. package/dist/validate.d.ts.map +1 -0
  83. package/dist/validate.js +180 -0
  84. package/dist/validate.js.map +1 -0
  85. package/package.json +23 -5
  86. package/LICENSE +0 -21
  87. package/README.md +0 -11
  88. package/index.js +0 -3
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @non-paper — OPTIONAL instrumentation helpers, NOT part of the paper.
3
+ *
4
+ * Nothing in this module appears in arXiv 2608.26263v3. The paper's §4.3
5
+ * methodology measures prompts in raw string chars (Average Prompt Size =
6
+ * mean char length per call, Total Token Cost = cumulative burn) and reports
7
+ * no tokenizer heuristic and no dollar pricing. Import from here only when
8
+ * you explicitly want a rough, clearly-labelled estimate outside the
9
+ * paper-exact baseline in `./token-tracker.js`.
10
+ */
11
+ /**
12
+ * @non-paper legacy heuristic: 1 token ≈ 4 chars, rounded up.
13
+ *
14
+ * Kept for backward compatibility of ad-hoc estimates only. Do NOT use it
15
+ * for paper §4.3 metrics — those are measured in chars (see `TokenTracker`).
16
+ * Empty text costs zero.
17
+ */
18
+ export class CharDiv4Counter {
19
+ count(text) {
20
+ if (text.length === 0) {
21
+ return 0;
22
+ }
23
+ return Math.ceil(text.length / 4);
24
+ }
25
+ }
26
+ /**
27
+ * @non-paper estimated dollar savings of state prompts vs the conversation
28
+ * baseline, from measured char counts (e.g. `TokenTracker.compareWithBaseline`).
29
+ *
30
+ * This is a back-of-the-envelope estimate, NOT a paper metric: the paper
31
+ * reports no pricing. `usdPerMillionChars` defaults to 3 (a placeholder
32
+ * rate, not a paper figure). Returns 0 when there is nothing to save.
33
+ */
34
+ export function estimateCostSavings(conversationChars, stateChars, usdPerMillionChars = 3) {
35
+ const saved = conversationChars - stateChars;
36
+ if (saved <= 0) {
37
+ return 0;
38
+ }
39
+ return (saved * usdPerMillionChars) / 1_000_000;
40
+ }
41
+ //# sourceMappingURL=instrumentation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instrumentation.js","sourceRoot":"","sources":["../src/instrumentation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAQH;;;;;;GAMG;AACH,MAAM,OAAO,eAAe;IAC1B,KAAK,CAAC,IAAY;QAChB,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtB,OAAO,CAAC,CAAC;QACX,CAAC;QACD,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACpC,CAAC;CACF;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CACjC,iBAAyB,EACzB,UAAkB,EAClB,kBAAkB,GAAG,CAAC;IAEtB,MAAM,KAAK,GAAG,iBAAiB,GAAG,UAAU,CAAC;IAC7C,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;QACf,OAAO,CAAC,CAAC;IACX,CAAC;IACD,OAAO,CAAC,KAAK,GAAG,kBAAkB,CAAC,GAAG,SAAS,CAAC;AAClD,CAAC"}
@@ -0,0 +1,34 @@
1
+ /** @non-paper log severity. */
2
+ export type LogLevel = 'info' | 'warn' | 'error';
3
+ /** @non-paper structured fields attached to a log line. */
4
+ export interface LogFields {
5
+ [key: string]: unknown;
6
+ }
7
+ /** @non-paper minimal structured logger consumed by the runtime. */
8
+ export interface Logger {
9
+ info(message: string, fields?: LogFields): void;
10
+ warn(message: string, fields?: LogFields): void;
11
+ error(message: string, fields?: LogFields): void;
12
+ }
13
+ /** @non-paper options for {@link JsonLogger}. All optional. */
14
+ export interface JsonLoggerOptions {
15
+ /** Line sink (default: `console.log`). One JSON object per call. */
16
+ sink?: (line: string) => void;
17
+ /** Millis source for `ts` (default: `Date.now`). */
18
+ now?: () => number;
19
+ }
20
+ /**
21
+ * @non-paper JSON-line `Logger`. The ENTRY is built first, then the whole
22
+ * serialized line is scrubbed — secrets hiding inside `fields` are caught
23
+ * exactly like secrets in the message.
24
+ */
25
+ export declare class JsonLogger implements Logger {
26
+ private readonly sink;
27
+ private readonly now;
28
+ constructor(options?: JsonLoggerOptions);
29
+ info(message: string, fields?: LogFields): void;
30
+ warn(message: string, fields?: LogFields): void;
31
+ error(message: string, fields?: LogFields): void;
32
+ private write;
33
+ }
34
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAcA,+BAA+B;AAC/B,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAEjD,2DAA2D;AAC3D,MAAM,WAAW,SAAS;IACxB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,oEAAoE;AACpE,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAChD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAChD,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;CAClD;AAED,+DAA+D;AAC/D,MAAM,WAAW,iBAAiB;IAChC,oEAAoE;IACpE,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,oDAAoD;IACpD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,qBAAa,UAAW,YAAW,MAAM;IACvC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyB;IAC9C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IAEnC,YAAY,OAAO,CAAC,EAAE,iBAAiB,EAGtC;IAED,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,GAAG,IAAI,CAE9C;IAED,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,GAAG,IAAI,CAE9C;IAED,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,GAAG,IAAI,CAE/C;IAED,OAAO,CAAC,KAAK;CAId"}
package/dist/logger.js ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @non-paper JSON-line logger with secret scrubbing.
3
+ *
4
+ * The paper logs nothing. This module adds an OPTIONAL, additive logging
5
+ * seam: the runtime calls it ONLY when a caller passes `logger?` (unset =
6
+ * silent, paper-exact). Every line is a single JSON object
7
+ * `{ level, msg, ts, ...fields }` passed through `redactSecrets`, so
8
+ * credential-shaped spans (tokens, keys, PEM blocks) can never leak via
9
+ * logs even when prompts/observations carry them.
10
+ *
11
+ * Zero dependencies, Node >= 20, ESM.
12
+ */
13
+ import { redactSecrets } from './redaction.js';
14
+ /**
15
+ * @non-paper JSON-line `Logger`. The ENTRY is built first, then the whole
16
+ * serialized line is scrubbed — secrets hiding inside `fields` are caught
17
+ * exactly like secrets in the message.
18
+ */
19
+ export class JsonLogger {
20
+ sink;
21
+ now;
22
+ constructor(options) {
23
+ this.sink = options?.sink ?? ((line) => console.log(line));
24
+ this.now = options?.now ?? (() => Date.now());
25
+ }
26
+ info(message, fields) {
27
+ this.write('info', message, fields);
28
+ }
29
+ warn(message, fields) {
30
+ this.write('warn', message, fields);
31
+ }
32
+ error(message, fields) {
33
+ this.write('error', message, fields);
34
+ }
35
+ write(level, message, fields) {
36
+ const entry = { level, msg: message, ts: this.now(), ...(fields ?? {}) };
37
+ this.sink(redactSecrets(JSON.stringify(entry)));
38
+ }
39
+ }
40
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAyB/C;;;;GAIG;AACH,MAAM,OAAO,UAAU;IACJ,IAAI,CAAyB;IAC7B,GAAG,CAAe;IAEnC,YAAY,OAA2B;QACrC,IAAI,CAAC,IAAI,GAAG,OAAO,EAAE,IAAI,IAAI,CAAC,CAAC,IAAY,EAAQ,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;QACzE,IAAI,CAAC,GAAG,GAAG,OAAO,EAAE,GAAG,IAAI,CAAC,GAAW,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IACxD,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,MAAkB;QACtC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IACtC,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,MAAkB;QACtC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IACtC,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,MAAkB;QACvC,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IACvC,CAAC;IAEO,KAAK,CAAC,KAAe,EAAE,OAAe,EAAE,MAAkB;QAChE,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAC;QACzE,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAClD,CAAC;CACF"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @non-paper versioned-state envelope + 0→1 migration.
3
+ *
4
+ * The paper has no persistence format; anything written to disk before this
5
+ * @non-paper layer is a BARE `SkillState` object (no envelope) — that is
6
+ * "version 0". The versioned envelope is `{ version: 1, state }`.
7
+ * `migrate` accepts unknown persisted JSON and returns a `VersionedState`:
8
+ *
9
+ * - `{ version: 1, state }` → returned as-is (deep-copied, never aliased);
10
+ * - `{ version: 0, state }` → re-enveloped as version 1;
11
+ * - a bare state object (no `version` key) → wrapped losslessly as v1;
12
+ * - anything else (null, arrays, primitives, wrong versions, non-object
13
+ * `state`) → throws; fail closed rather than running on garbage.
14
+ *
15
+ * Pure function, zero dependencies, Node >= 20, ESM.
16
+ */
17
+ import type { SkillState } from './types.js';
18
+ /** Current @non-paper persistence envelope version. */
19
+ export declare const CURRENT_STATE_VERSION: 1;
20
+ /**
21
+ * @non-paper versioned envelope around a paper-exact `SkillState`.
22
+ * The inner `state` keeps full paper semantics (⊕ merge, null-deletion);
23
+ * the envelope only versions the BYTES on disk.
24
+ */
25
+ export interface VersionedState {
26
+ version: 1;
27
+ state: SkillState;
28
+ }
29
+ /**
30
+ * Normalize unknown persisted JSON into a `VersionedState` (0→1).
31
+ * Never aliases its input: the returned `state` is always a deep copy.
32
+ * Throws on anything that is not recognizably a state.
33
+ */
34
+ export declare function migrate(raw: unknown): VersionedState;
35
+ //# sourceMappingURL=migrations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7C,uDAAuD;AACvD,eAAO,MAAM,qBAAqB,EAAG,CAAU,CAAC;AAEhD;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,CAAC,CAAC;IACX,KAAK,EAAE,UAAU,CAAC;CACnB;AAMD;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,CAapD"}
@@ -0,0 +1,26 @@
1
+ import { clone } from './clock.js';
2
+ /** Current @non-paper persistence envelope version. */
3
+ export const CURRENT_STATE_VERSION = 1;
4
+ function isRecord(value) {
5
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
6
+ }
7
+ /**
8
+ * Normalize unknown persisted JSON into a `VersionedState` (0→1).
9
+ * Never aliases its input: the returned `state` is always a deep copy.
10
+ * Throws on anything that is not recognizably a state.
11
+ */
12
+ export function migrate(raw) {
13
+ if (isRecord(raw)) {
14
+ if (raw.version === CURRENT_STATE_VERSION && isRecord(raw.state)) {
15
+ return { version: 1, state: clone(raw.state) };
16
+ }
17
+ if (raw.version === 0 && isRecord(raw.state)) {
18
+ return { version: 1, state: clone(raw.state) };
19
+ }
20
+ if (!('version' in raw)) {
21
+ return { version: 1, state: clone(raw) };
22
+ }
23
+ }
24
+ throw new Error('Unrecognized state format: cannot migrate to version 1');
25
+ }
26
+ //# sourceMappingURL=migrations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrations.js","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,uDAAuD;AACvD,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAU,CAAC;AAYhD,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAAC,GAAY;IAClC,IAAI,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAClB,IAAI,GAAG,CAAC,OAAO,KAAK,qBAAqB,IAAI,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YACjE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,KAAmB,CAAC,EAAE,CAAC;QAC/D,CAAC;QACD,IAAI,GAAG,CAAC,OAAO,KAAK,CAAC,IAAI,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAC7C,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,KAAmB,CAAC,EAAE,CAAC;QAC/D,CAAC;QACD,IAAI,CAAC,CAAC,SAAS,IAAI,GAAG,CAAC,EAAE,CAAC;YACxB,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,GAAiB,CAAC,EAAE,CAAC;QACzD,CAAC;IACH,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;AAC5E,CAAC"}
@@ -0,0 +1,106 @@
1
+ import type { ProceduralSpec, SkillState, Observation, StatePatch, StateSchema } from './types.js';
2
+ export interface PromptTransformerOptions {
3
+ platform?: 'claude' | 'opencode' | 'generic';
4
+ }
5
+ /**
6
+ * Reason a parseResponse call failed.
7
+ *
8
+ * @non-paper — implementation-internal codes. These are NOT the paper's
9
+ * §5.7 taxonomy: §5.7 reports log-analysis categories from the Gemma-4-31B
10
+ * T=100 runs (68% Premature Overwrite/Deletion, 20% Schema/Type Coercion,
11
+ * 12% JSON Syntax), not parser result codes.
12
+ */
13
+ export type ParseFailureReason = 'no_block' | 'malformed_json' | 'missing_state_patch' | 'missing_action';
14
+ /**
15
+ * Typed result of parsing an LLM response into a state patch + action.
16
+ */
17
+ export type ParseResponseResult = {
18
+ ok: true;
19
+ patch: StatePatch;
20
+ action: string;
21
+ } | {
22
+ ok: false;
23
+ reason: ParseFailureReason;
24
+ detail?: string;
25
+ };
26
+ /**
27
+ * Transforms skill state, observations, and specs into formatted prompts
28
+ * for LLM consumption, and parses structured responses back.
29
+ *
30
+ * Only {@link PromptTransformer.formatPaper} is paper-exact (Appendix A.4).
31
+ * Every other formatter here is an implementation convenience for the
32
+ * platform adapters — the paper defines no per-platform prompt templates.
33
+ */
34
+ export declare class PromptTransformer {
35
+ private platform;
36
+ constructor(options?: PromptTransformerOptions);
37
+ /**
38
+ * Format the full prompt. Delegates to platform-specific formatter.
39
+ *
40
+ * @non-paper — adapter convenience. Paper-exact callers must use
41
+ * {@link PromptTransformer.formatPaper}.
42
+ */
43
+ formatPrompt(spec: ProceduralSpec, state: SkillState, observation: Observation, platform?: 'claude' | 'opencode' | 'generic'): string;
44
+ /**
45
+ * Claude-specific prompt format: markdown with system prompt section,
46
+ * state section, observation section, instruction for reasoning + JSON.
47
+ *
48
+ * @non-paper — adapter convenience; the paper defines no Claude template.
49
+ */
50
+ formatForClaude(spec: ProceduralSpec, state: SkillState, observation: Observation): string;
51
+ /**
52
+ * OpenCode-specific prompt format adapted for the opencode skill system.
53
+ *
54
+ * @non-paper — adapter convenience; the paper defines no OpenCode template.
55
+ */
56
+ formatForOpenCode(spec: ProceduralSpec, state: SkillState, observation: Observation): string;
57
+ /**
58
+ * Paper-exact prompt format — byte-verbatim Appendix A.4.
59
+ *
60
+ * The template is reproduced exactly as printed in the paper (blank lines
61
+ * and indentation preserved): instructions start after a blank line, the
62
+ * state is fenced as ```json with compact JSON (`JSON.stringify(state)` —
63
+ * the paper's `json.dumps(state, separators=(',', ':'))`), and the response
64
+ * directive carries the two-key JSON contract verbatim. No schema
65
+ * description and no platform padding is added on top of A.4.
66
+ */
67
+ formatPaper(spec: ProceduralSpec, state: SkillState, observation: Observation): string;
68
+ /**
69
+ * Generic prompt format (no platform prefix).
70
+ *
71
+ * @non-paper — adapter convenience; the paper-exact template is
72
+ * {@link PromptTransformer.formatPaper} (Appendix A.4).
73
+ */
74
+ private formatGeneric;
75
+ /**
76
+ * Extract the state_patch from an LLM response containing a fenced JSON block.
77
+ */
78
+ extractStatePatch(response: string): StatePatch | null;
79
+ /**
80
+ * Extract the action string from an LLM response containing a fenced JSON block.
81
+ */
82
+ extractAction(response: string): string | null;
83
+ /**
84
+ * Parse an LLM response into a typed result: either a valid
85
+ * { patch, action } pair or a structured failure with a reason.
86
+ *
87
+ * Malformed outputs can never corrupt Σt: callers (runtime §7
88
+ * rollback-retry, adapter hook scripts) must reject `ok: false` results
89
+ * without touching state (paper Limitations).
90
+ */
91
+ parseResponse(response: string): ParseResponseResult;
92
+ /**
93
+ * Serialize state as JSON. When a schema is provided, only keys present
94
+ * in the schema are serialized (schema-aware filtering — unknown keys,
95
+ * including any stray 'reasoning' key, are dropped). Without a schema,
96
+ * all keys are serialized as-is.
97
+ */
98
+ serializeState(state: SkillState, options?: {
99
+ pretty?: boolean;
100
+ }, schema?: StateSchema): string;
101
+ /**
102
+ * Describe the schema fields for inclusion in prompts.
103
+ */
104
+ private describeSchema;
105
+ }
106
+ //# sourceMappingURL=prompt-transformer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-transformer.d.ts","sourceRoot":"","sources":["../src/prompt-transformer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,UAAU,EACV,WAAW,EACX,UAAU,EACV,WAAW,EACZ,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,EAAE,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;CAC9C;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,GAC1B,UAAU,GACV,gBAAgB,GAChB,qBAAqB,GACrB,gBAAgB,CAAC;AAErB;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAC3B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC/C;IACE,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,kBAAkB,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAMN;;;;;;;GAOG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAoC;IAEpD,YAAY,OAAO,CAAC,EAAE,wBAAwB,EAE7C;IAED;;;;;OAKG;IACH,YAAY,CACV,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,UAAU,EACjB,WAAW,EAAE,WAAW,EACxB,QAAQ,CAAC,EAAE,QAAQ,GAAG,UAAU,GAAG,SAAS,GAC3C,MAAM,CASR;IAED;;;;;OAKG;IACH,eAAe,CACb,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,UAAU,EACjB,WAAW,EAAE,WAAW,GACvB,MAAM,CAmCR;IAED;;;;OAIG;IACH,iBAAiB,CACf,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,UAAU,EACjB,WAAW,EAAE,WAAW,GACvB,MAAM,CAyBR;IAED;;;;;;;;;OASG;IACH,WAAW,CACT,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,UAAU,EACjB,WAAW,EAAE,WAAW,GACvB,MAAM,CAmBR;IAED;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IAqCrB;;OAEG;IACH,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAGrD;IAED;;OAEG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG7C;IAED;;;;;;;OAOG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,mBAAmB,CA2CnD;IAMD;;;;;OAKG;IACH,cAAc,CACZ,KAAK,EAAE,UAAU,EACjB,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,EAC9B,MAAM,CAAC,EAAE,WAAW,GACnB,MAAM,CAcR;IAED;;OAEG;IACH,OAAO,CAAC,cAAc;CAMvB"}
@@ -0,0 +1,265 @@
1
+ function isPlainObject(value) {
2
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
3
+ }
4
+ /**
5
+ * Transforms skill state, observations, and specs into formatted prompts
6
+ * for LLM consumption, and parses structured responses back.
7
+ *
8
+ * Only {@link PromptTransformer.formatPaper} is paper-exact (Appendix A.4).
9
+ * Every other formatter here is an implementation convenience for the
10
+ * platform adapters — the paper defines no per-platform prompt templates.
11
+ */
12
+ export class PromptTransformer {
13
+ platform;
14
+ constructor(options) {
15
+ this.platform = options?.platform ?? 'generic';
16
+ }
17
+ /**
18
+ * Format the full prompt. Delegates to platform-specific formatter.
19
+ *
20
+ * @non-paper — adapter convenience. Paper-exact callers must use
21
+ * {@link PromptTransformer.formatPaper}.
22
+ */
23
+ formatPrompt(spec, state, observation, platform) {
24
+ const p = platform ?? this.platform;
25
+ if (p === 'claude') {
26
+ return this.formatForClaude(spec, state, observation);
27
+ }
28
+ if (p === 'opencode') {
29
+ return this.formatForOpenCode(spec, state, observation);
30
+ }
31
+ return this.formatGeneric(spec, state, observation);
32
+ }
33
+ /**
34
+ * Claude-specific prompt format: markdown with system prompt section,
35
+ * state section, observation section, instruction for reasoning + JSON.
36
+ *
37
+ * @non-paper — adapter convenience; the paper defines no Claude template.
38
+ */
39
+ formatForClaude(spec, state, observation) {
40
+ const stateJson = this.serializeState(state, undefined, spec.schema);
41
+ const schemaDesc = this.describeSchema(spec.schema);
42
+ return `# System
43
+
44
+ You are ${spec.name}. ${spec.instructions}
45
+
46
+ ${schemaDesc}
47
+
48
+ # Current State
49
+
50
+ \`\`\`json
51
+ ${stateJson}
52
+ \`\`\`
53
+
54
+ # Observation
55
+
56
+ ${observation.content}
57
+
58
+ # Instructions
59
+
60
+ Based on the observation and your current state, provide your response with:
61
+
62
+ 1. Step-by-step reasoning (will be discarded after execution)
63
+ 2. A JSON block containing both your State Patch and your Action. The JSON block MUST have exactly these two keys:
64
+
65
+ \`\`\`json
66
+ {
67
+ "state_patch": { "key": "new_value", "obsolete_key": null },
68
+ "action": "your_action_here"
69
+ }
70
+ \`\`\`
71
+
72
+ In \`state_patch\`, set keys to null to delete them. Only include fields you want to change. Omit fields to leave them unchanged.`;
73
+ }
74
+ /**
75
+ * OpenCode-specific prompt format adapted for the opencode skill system.
76
+ *
77
+ * @non-paper — adapter convenience; the paper defines no OpenCode template.
78
+ */
79
+ formatForOpenCode(spec, state, observation) {
80
+ const stateJson = this.serializeState(state, undefined, spec.schema);
81
+ const schemaDesc = this.describeSchema(spec.schema);
82
+ return `<skill name="${spec.id}">
83
+ <instructions>${spec.instructions}</instructions>
84
+ ${schemaDesc}
85
+ <state>
86
+ ${stateJson}
87
+ </state>
88
+ <observation>
89
+ ${observation.content}
90
+ </observation>
91
+ </skill>
92
+
93
+ Respond with step-by-step reasoning followed by a JSON block containing both your State Patch and your Action. The JSON block MUST have exactly these two keys:
94
+
95
+ \`\`\`json
96
+ {
97
+ "state_patch": { "key": "new_value", "obsolete_key": null },
98
+ "action": "action_name"
99
+ }
100
+ \`\`\`
101
+
102
+ In \`state_patch\`, set keys to null to delete them. Only include fields you want to change. Omit fields to leave them unchanged.`;
103
+ }
104
+ /**
105
+ * Paper-exact prompt format — byte-verbatim Appendix A.4.
106
+ *
107
+ * The template is reproduced exactly as printed in the paper (blank lines
108
+ * and indentation preserved): instructions start after a blank line, the
109
+ * state is fenced as ```json with compact JSON (`JSON.stringify(state)` —
110
+ * the paper's `json.dumps(state, separators=(',', ':'))`), and the response
111
+ * directive carries the two-key JSON contract verbatim. No schema
112
+ * description and no platform padding is added on top of A.4.
113
+ */
114
+ formatPaper(spec, state, observation) {
115
+ const stateJson = JSON.stringify(state);
116
+ return `Instructions:
117
+
118
+ ${spec.instructions}
119
+
120
+ Skill Execution State:
121
+
122
+ \`\`\`json
123
+ ${stateJson}
124
+ \`\`\`
125
+ Latest Observation: ${observation.content}
126
+
127
+ Provide your response with:
128
+
129
+ 1. Step-by-step reasoning (will be discarded after execution)
130
+
131
+ 2. A JSON block fenced with json ... containing both your State Patch and your Action. The JSON block MUST have exactly these two keys: { "state_patch": { <dict: your state updates, set keys to null to delete> }, "action": "<string: the exact command you want to execute>" }`;
132
+ }
133
+ /**
134
+ * Generic prompt format (no platform prefix).
135
+ *
136
+ * @non-paper — adapter convenience; the paper-exact template is
137
+ * {@link PromptTransformer.formatPaper} (Appendix A.4).
138
+ */
139
+ formatGeneric(spec, state, observation) {
140
+ const stateJson = this.serializeState(state, undefined, spec.schema);
141
+ const schemaDesc = this.describeSchema(spec.schema);
142
+ return `${spec.instructions}
143
+
144
+ ${schemaDesc}
145
+
146
+ ## Current State
147
+
148
+ ${stateJson}
149
+
150
+ ## Observation
151
+
152
+ ${observation.content}
153
+
154
+ ## Required Output
155
+
156
+ Provide your response with:
157
+
158
+ 1. Step-by-step reasoning (will be discarded after execution)
159
+ 2. A JSON block containing both your State Patch and your Action. The JSON block MUST have exactly these two keys:
160
+
161
+ \`\`\`json
162
+ {
163
+ "state_patch": { "key": "value", "obsolete_key": null },
164
+ "action": "action_name"
165
+ }
166
+ \`\`\`
167
+
168
+ In \`state_patch\`, set keys to null to delete them.`;
169
+ }
170
+ /**
171
+ * Extract the state_patch from an LLM response containing a fenced JSON block.
172
+ */
173
+ extractStatePatch(response) {
174
+ const result = this.parseResponse(response);
175
+ return result.ok ? result.patch : null;
176
+ }
177
+ /**
178
+ * Extract the action string from an LLM response containing a fenced JSON block.
179
+ */
180
+ extractAction(response) {
181
+ const result = this.parseResponse(response);
182
+ return result.ok ? result.action : null;
183
+ }
184
+ /**
185
+ * Parse an LLM response into a typed result: either a valid
186
+ * { patch, action } pair or a structured failure with a reason.
187
+ *
188
+ * Malformed outputs can never corrupt Σt: callers (runtime §7
189
+ * rollback-retry, adapter hook scripts) must reject `ok: false` results
190
+ * without touching state (paper Limitations).
191
+ */
192
+ parseResponse(response) {
193
+ // Prefer a closed ```json fence.
194
+ //
195
+ // @non-paper extension: fall back to an unterminated fence (common
196
+ // with truncated LLM output) and attempt to parse the rest. The paper
197
+ // specifies only the fenced JSON block; lenient recovery is ours.
198
+ const closed = response.match(/```json[ \t]*\n?([\s\S]*?)\n?[ \t]*```/);
199
+ const opened = closed ? null : response.match(/```json[ \t]*\n?([\s\S]*)$/);
200
+ const block = closed ? closed[1] : opened ? opened[1] : null;
201
+ if (block === null) {
202
+ return { ok: false, reason: 'no_block' };
203
+ }
204
+ let parsed;
205
+ try {
206
+ parsed = JSON.parse(block);
207
+ }
208
+ catch (error) {
209
+ // String(error) renders "SyntaxError: <message>" — includes the
210
+ // parse error message from JSON.parse.
211
+ return {
212
+ ok: false,
213
+ reason: 'malformed_json',
214
+ detail: String(error),
215
+ };
216
+ }
217
+ if (!isPlainObject(parsed)) {
218
+ // A fenced block containing a bare primitive or null has no
219
+ // state_patch object in it.
220
+ return { ok: false, reason: 'missing_state_patch' };
221
+ }
222
+ const { state_patch: statePatch, action } = parsed;
223
+ if (!isPlainObject(statePatch)) {
224
+ return { ok: false, reason: 'missing_state_patch' };
225
+ }
226
+ if (typeof action !== 'string') {
227
+ return { ok: false, reason: 'missing_action' };
228
+ }
229
+ return { ok: true, patch: statePatch, action };
230
+ }
231
+ /* ------------------------------------------------------------------ */
232
+ /* Internal helpers */
233
+ /* ------------------------------------------------------------------ */
234
+ /**
235
+ * Serialize state as JSON. When a schema is provided, only keys present
236
+ * in the schema are serialized (schema-aware filtering — unknown keys,
237
+ * including any stray 'reasoning' key, are dropped). Without a schema,
238
+ * all keys are serialized as-is.
239
+ */
240
+ serializeState(state, options, schema) {
241
+ let toSerialize = state;
242
+ if (schema) {
243
+ toSerialize = {};
244
+ for (const key of Object.keys(state)) {
245
+ if (key in schema) {
246
+ toSerialize[key] = state[key];
247
+ }
248
+ }
249
+ }
250
+ if (options?.pretty) {
251
+ return JSON.stringify(toSerialize, null, 2);
252
+ }
253
+ return JSON.stringify(toSerialize);
254
+ }
255
+ /**
256
+ * Describe the schema fields for inclusion in prompts.
257
+ */
258
+ describeSchema(schema) {
259
+ const fields = Object.entries(schema)
260
+ .map(([name, field]) => `- ${name} (${field.type}): ${field.description ?? 'no description'}`)
261
+ .join('\n');
262
+ return `## Schema\n${fields}`;
263
+ }
264
+ }
265
+ //# sourceMappingURL=prompt-transformer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-transformer.js","sourceRoot":"","sources":["../src/prompt-transformer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @non-paper LLM provider seam (Wave 4 DX).
3
+ *
4
+ * The paper core calls a bare `LLMFn` (`prompt => response text`) and
5
+ * measures raw string chars itself (§4.3). This module adds an OPTIONAL,
6
+ * additive provider interface for hosts that already report usage:
7
+ *
8
+ * - `LLMProvider.call(prompt, opts?)` resolves `{ text, usage? }` where
9
+ * `usage.promptChars` / `usage.completionChars` are RAW STRING CHARS
10
+ * (§4.3) reported by the caller — never tokenizer output;
11
+ * - `fromLLMFn(fn)` wraps a legacy `LLMFn` into an `LLMProvider`
12
+ * (backwards compatibility; `LLMFn` itself is NOT removed);
13
+ * - `isLLMProvider(v)` distinguishes `LLMFn | LLMProvider` at runtime
14
+ * (function = legacy fn, object with a `call` function = provider).
15
+ *
16
+ * The runtime accepts `llm: LLMFn | LLMProvider`: with a provider it
17
+ * prefers `usage` over measuring strings, otherwise it measures exactly
18
+ * as before. Zero dependencies, Node >= 20, ESM.
19
+ */
20
+ /** Legacy LLM function: prompt in, raw response text out. Kept verbatim. */
21
+ export type LLMFnLike = (prompt: string) => Promise<string>;
22
+ /** @non-paper per-call options threaded into `LLMProvider.call`. */
23
+ export interface LLMCallOptions {
24
+ /** AbortSignal for the in-flight call (runtime forwards its `signal?`). */
25
+ signal?: AbortSignal;
26
+ }
27
+ /**
28
+ * @non-paper usage reported by the provider, in raw string CHARS (§4.3).
29
+ * Both fields are optional: missing values fall back to measuring the
30
+ * corresponding string, exactly the legacy `LLMFn` behavior.
31
+ */
32
+ export interface LLMUsage {
33
+ /** Raw chars of the prompt as sent (overrides `prompt.length`). */
34
+ promptChars?: number;
35
+ /** Raw chars of the completion text (overrides `text.length`). */
36
+ completionChars?: number;
37
+ }
38
+ /** @non-paper provider result: raw text plus optional char usage. */
39
+ export interface LLMResult {
40
+ text: string;
41
+ usage?: LLMUsage;
42
+ }
43
+ /**
44
+ * @non-paper LLM provider. `call` resolves the raw response text and,
45
+ * when known, its char sizes — so metered hosts are not measured twice.
46
+ */
47
+ export interface LLMProvider {
48
+ call(prompt: string, opts?: LLMCallOptions): Promise<LLMResult>;
49
+ }
50
+ /**
51
+ * @non-paper runtime guard: an object with a callable `call` is a
52
+ * provider; anything else passed as `llm` is treated as a legacy `LLMFn`.
53
+ */
54
+ export declare function isLLMProvider(value: unknown): value is LLMProvider;
55
+ /**
56
+ * @non-paper backwards-compatibility adapter: wrap a legacy `LLMFn`
57
+ * into an `LLMProvider`. The wrapper honors an already-aborted `signal`
58
+ * (rejects with `signal.reason`) and otherwise delegates verbatim —
59
+ * no usage is synthesized, so the runtime measures strings as before.
60
+ */
61
+ export declare function fromLLMFn(fn: LLMFnLike): LLMProvider;
62
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,4EAA4E;AAC5E,MAAM,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAE5D,oEAAoE;AACpE,MAAM,WAAW,cAAc;IAC7B,2EAA2E;IAC3E,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,QAAQ;IACvB,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kEAAkE;IAClE,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,qEAAqE;AACrE,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,QAAQ,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;CACjE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAKlE;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,SAAS,GAAG,WAAW,CASpD"}