dsh-hashline-edit 0.2.0 → 0.2.2

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 (109) hide show
  1. package/README.md +59 -55
  2. package/cordis.patch.yml +1 -1
  3. package/lib/index.js +7225 -6445
  4. package/lib/types/dsh/contracts.d.ts +3 -12
  5. package/lib/types/dsh/fs.d.ts +0 -3
  6. package/lib/types/dsh/guidance.d.ts +5 -5
  7. package/lib/types/dsh/host-compat.d.ts +78 -7
  8. package/lib/types/dsh/index.d.ts +7 -17
  9. package/lib/types/dsh/metrics/events.d.ts +1 -1
  10. package/lib/types/dsh/metrics/observer.d.ts +1 -1
  11. package/lib/types/dsh/sandbox.d.ts +2 -2
  12. package/lib/types/dsh/shell-hook.d.ts +1 -1
  13. package/lib/types/dsh/tools/edit.d.ts +10 -0
  14. package/lib/types/dsh/tools/read.d.ts +3 -3
  15. package/lib/types/src/domain-errors.d.ts +2 -16
  16. package/lib/types/src/edit-tool.d.ts +41 -0
  17. package/lib/types/src/edit-undo.d.ts +2 -11
  18. package/lib/types/src/file-content/index.d.ts +1 -1
  19. package/lib/types/src/file-content/preview.d.ts +1 -1
  20. package/lib/types/src/hash-store.d.ts +2 -2
  21. package/lib/types/src/hashline/alphabet.d.ts +16 -1
  22. package/lib/types/src/hashline/hash-identity.d.ts +10 -4
  23. package/lib/types/src/mutation-engine/engine.d.ts +31 -0
  24. package/lib/types/src/mutation-engine/pipeline.d.ts +1 -8
  25. package/lib/types/src/mutation-engine/types.d.ts +50 -40
  26. package/lib/types/src/payload-contract.d.ts +135 -0
  27. package/lib/types/src/served-session/serve-render.d.ts +0 -6
  28. package/lib/types/src/undo-store.d.ts +1 -17
  29. package/package.json +21 -10
  30. package/prompts/edit-guidelines.md +10 -0
  31. package/prompts/edit-snippet.md +1 -0
  32. package/prompts/edit.md +1 -0
  33. package/prompts/read-guidelines.md +4 -4
  34. package/prompts/read.md +1 -1
  35. package/prompts/undo-last-edit-guidelines.md +2 -2
  36. package/prompts/undo-last-edit-snippet.md +1 -1
  37. package/prompts/undo-last-edit.md +1 -1
  38. package/src/bash-classifier.ts +1626 -0
  39. package/src/bash-conversion/decide.ts +812 -0
  40. package/src/bash-display-transform.ts +50 -0
  41. package/src/bash-multifile-config.ts +239 -0
  42. package/src/bash-view-config.ts +158 -0
  43. package/src/cut-repair.ts +11 -77
  44. package/src/domain-errors.ts +3 -35
  45. package/src/edit-normalize.ts +8 -0
  46. package/src/edit-render.ts +161 -0
  47. package/src/edit-tool.ts +118 -0
  48. package/src/edit-undo.ts +25 -379
  49. package/src/edit.ts +136 -0
  50. package/src/file-content/index.ts +1 -1
  51. package/src/file-content/preview.ts +1 -1
  52. package/src/file-content/served-header.ts +1 -1
  53. package/src/hash-store.ts +1 -18
  54. package/src/hashline/alphabet.ts +42 -1
  55. package/src/hashline/hash-identity.ts +12 -3
  56. package/src/integrations/pi-lens/command.ts +712 -0
  57. package/src/integrations/pi-lens/config.ts +148 -0
  58. package/src/integrations/pi-lens/io-bridge-adapter.ts +234 -0
  59. package/src/integrations/pi-lens/io-bridge.ts +112 -0
  60. package/src/integrations/pi-lens/settings-overlay.ts +272 -0
  61. package/src/lifecycle-hooks/index.ts +1992 -0
  62. package/src/lifecycle-hooks/types.ts +64 -0
  63. package/src/mutated-files.ts +75 -0
  64. package/src/mutation-engine/engine.ts +239 -0
  65. package/src/mutation-engine/index.ts +17 -0
  66. package/src/mutation-engine/pipeline.ts +27 -198
  67. package/src/mutation-engine/types.ts +57 -39
  68. package/src/mutation-queue.ts +1 -1
  69. package/src/payload-contract.ts +685 -0
  70. package/src/preview-controller.ts +84 -0
  71. package/src/prompts.ts +43 -10
  72. package/src/read.ts +865 -0
  73. package/src/search-hook.ts +41 -0
  74. package/src/served-session/serve-render.ts +2 -2
  75. package/src/store-config.ts +73 -0
  76. package/src/store-janitor-warning.ts +169 -0
  77. package/src/store-janitor.ts +539 -0
  78. package/src/store-kernel/settings.ts +233 -0
  79. package/src/store.ts +154 -0
  80. package/src/tui-presenter.ts +134 -0
  81. package/src/undo-store.ts +5 -74
  82. package/src/write-hook.ts +130 -0
  83. package/lib/types/dsh/hide.d.ts +0 -78
  84. package/lib/types/dsh/tools/apply-patch.d.ts +0 -38
  85. package/lib/types/src/apply-patch/commit.d.ts +0 -49
  86. package/lib/types/src/apply-patch/grammar.d.ts +0 -29
  87. package/lib/types/src/apply-patch/lifecycle.d.ts +0 -32
  88. package/lib/types/src/apply-patch/plan.d.ts +0 -50
  89. package/lib/types/src/apply-patch/render.d.ts +0 -51
  90. package/lib/types/src/apply-patch/resolve.d.ts +0 -129
  91. package/lib/types/src/apply-patch/tool.d.ts +0 -54
  92. package/lib/types/src/apply-patch/types.d.ts +0 -362
  93. package/lib/types/src/fs-lifecycle.d.ts +0 -44
  94. package/lib/types/src/host-utils.d.ts +0 -63
  95. package/lib/types/src/mutation-engine/edit-request.d.ts +0 -34
  96. package/prompts/apply-patch-guidelines.md +0 -10
  97. package/prompts/apply-patch.md +0 -1
  98. package/src/apply-patch/commit.ts +0 -190
  99. package/src/apply-patch/grammar.ts +0 -620
  100. package/src/apply-patch/index.ts +0 -17
  101. package/src/apply-patch/lifecycle.ts +0 -195
  102. package/src/apply-patch/plan.ts +0 -800
  103. package/src/apply-patch/render.ts +0 -137
  104. package/src/apply-patch/resolve.ts +0 -470
  105. package/src/apply-patch/tool.ts +0 -257
  106. package/src/apply-patch/types.ts +0 -387
  107. package/src/fs-lifecycle.ts +0 -201
  108. package/src/host-utils.ts +0 -224
  109. package/src/mutation-engine/edit-request.ts +0 -39
@@ -7,9 +7,10 @@
7
7
  import type { LineEnding } from "../edit-diff.js";
8
8
  import type { ResolvedRange } from "../served-session/serve-render.js";
9
9
  import type { HashStore } from "../hash-store.js";
10
+ import type { EditDetails, RMetrics } from "../edit-response.js";
11
+ import type { DomainErrorCode, RangeCause } from "../domain-errors.js";
12
+ import type { ServedRow } from "../domain-errors.js";
10
13
  export interface PipelineOptions {
11
- /** Workspace root used to re-assert lifecycle path boundaries at commit time. */
12
- workspaceRoot?: string;
13
14
  accessMode?: number;
14
15
  signal?: AbortSignal;
15
16
  store?: HashStore;
@@ -31,21 +32,6 @@ export interface PipelineOptions {
31
32
  * window cannot show that.
32
33
  */
33
34
  onBeforeFirstCutWrite?: () => void | Promise<void>;
34
- /**
35
- * TEST-ONLY mid-member fault seam: called after a move's rename is durable and before its
36
- * destination content is rewritten, so the rollback of that in-flight member is exercised.
37
- */
38
- onAfterMoveRename?: (toAbsolutePath: string) => void | Promise<void>;
39
- }
40
- /**
41
- * The honest outcome of a failed multi-member commit's rollback attempt. `restored` members are
42
- * proven back at their pre-images; `remaining` members were not rolled back; `uncertain` members
43
- * are those whose rollback attempt itself threw, so neither state can be claimed for them.
44
- */
45
- export interface TransactionRecoveryReport {
46
- restored: string[];
47
- remaining: string[];
48
- uncertain: string[];
49
35
  }
50
36
  /**
51
37
  * (ticket-04b) One foreign `mode: "cut"` item as seen by the pre-pass: the submitted span anchors
@@ -59,26 +45,6 @@ export interface ForeignCutRecord {
59
45
  spanFrom: string;
60
46
  spanTo: string;
61
47
  }
62
- /**
63
- * The file-lifecycle action one transaction member carries.
64
- *
65
- * This is the engine-local vocabulary: apply_patch's plan IR maps onto it in
66
- * `src/apply-patch/commit.ts` so the mutation engine never imports the apply-patch layer.
67
- * Absent is an ordinary in-place edit.
68
- */
69
- export type MemberLifecycle = {
70
- kind: "keep";
71
- } | {
72
- kind: "add";
73
- createdDirs: readonly string[];
74
- } | {
75
- kind: "delete";
76
- } | {
77
- kind: "move";
78
- fromAbsolutePath: string;
79
- toAbsolutePath: string;
80
- createdDirs: readonly string[];
81
- };
82
48
  export interface ProcessedEditFile {
83
49
  path: string;
84
50
  absolutePath: string;
@@ -122,9 +88,53 @@ export interface ProcessedEditFile {
122
88
  * correlated two-file transaction; the in-memory buffer itself never touches these files.
123
89
  */
124
90
  foreignCuts: ForeignCutRecord[];
91
+ }
92
+ export interface MutationSuccess {
93
+ ok: true;
94
+ /** SAFETY: Normalized result content (LF). */
95
+ result: string;
96
+ /** SAFETY: Unified diff (hash-anchored) for model consumption. */
97
+ diff: string;
98
+ /** SAFETY: User-facing drift notice, if any (details only, not model content). */
99
+ drift: string | undefined;
100
+ /** SAFETY: Metrics for telemetry. */
101
+ metrics: RMetrics;
102
+ raw: ProcessedEditFile;
103
+ /** SAFETY: Full tool result (content + details) for pi's tool_result hook. */
104
+ toolResult: {
105
+ content: Array<{
106
+ type: "text";
107
+ text: string;
108
+ }>;
109
+ details: EditDetails;
110
+ };
111
+ }
112
+ export interface MutationFailure {
113
+ ok: false;
114
+ /**
115
+ * SAFETY: Machine code — a registry member (`DomainErrorCode`). `toFailure` validates every
116
+ * caught code through the envelope reader (`src/error-envelope.ts`): non-registry strings
117
+ * (errno pass-through included) route to `E_UNKNOWN`, so the union is honest.
118
+ */
119
+ code: DomainErrorCode;
120
+ /** SAFETY: Human message — model-facing signal when applicable. */
121
+ message: string;
122
+ /** SAFETY: Fresh served block for retry when available (reject-and-serve). */
123
+ servedBlock?: string;
124
+ servedRows?: ServedRow[];
125
125
  /**
126
- * apply_patch lifecycle action for this member; absent is an ordinary edit. The correlated
127
- * transaction reads it for pre-image capture, commit ordering, rollback and undo-row shape.
126
+ * SAFETY: the RAW `E_BAD_PAYLOAD` message behind a rendered refusal, when the route is one, so
127
+ * SAFETY: the tool seam can name the invoking tool instead of falling back to the neutral
128
+ * SAFETY: wording. Present only for `code: "E_BAD_PAYLOAD"`.
128
129
  */
129
- lifecycle?: MemberLifecycle;
130
+ payloadMessage?: string;
131
+ /** SAFETY: User-facing diagnosis, never a model remedy — a GLOSSARY.md glossary term. */
132
+ cause?: RangeCause;
133
+ details?: {
134
+ code: DomainErrorCode;
135
+ cause: RangeCause;
136
+ };
130
137
  }
138
+ export type MutationResult = MutationSuccess | MutationFailure;
139
+ export declare function isMutationSuccess(r: MutationResult): r is MutationSuccess;
140
+ export declare function isMutationFailure(r: MutationResult): r is MutationFailure;
@@ -0,0 +1,135 @@
1
+ import { Type } from "typebox";
2
+ import type { EditPlacement } from "./hashline/resolve.js";
3
+ declare const normalizedEdit: unique symbol;
4
+ /** Width-appropriate illustrative anchor for prompt examples (width is a build-time setting, ADR-0020). */
5
+ export declare const ANCHOR_EXAMPLE: string;
6
+ /**
7
+ * The `text_ref` payload: two inclusive bare anchors bounding a served span, an optional serving
8
+ * `file`, and a REQUIRED `mode` — `"copy"` re-inserts the span bytes, `"cut"` additionally retires
9
+ * them. Both modes apply to this file AND to another served file (ticket-04b): a foreign-source
10
+ * `"cut"` commits the insert and the source retirement as one correlated transaction (ADR-0028).
11
+ */
12
+ type SpanRef = {
13
+ anchor_from: string;
14
+ anchor_to: string;
15
+ file?: string;
16
+ mode: "copy" | "cut";
17
+ };
18
+ /**
19
+ * Wire item (ticket-04): exactly one payload per item — `text` or `text_ref` — over the inclusive
20
+ * anchor pair, with optional `at` placement. There is no `op` field and no verbs: the key set IS
21
+ * the vocabulary (ADR-0007 line 26; content comes from the copy of another served file, which the
22
+ * wire expresses as `text_ref.file`).
23
+ */
24
+ export type EditItem = {
25
+ anchor_from: string;
26
+ anchor_to: string;
27
+ at?: "in-place" | "before" | "after";
28
+ text?: string;
29
+ text_ref?: SpanRef;
30
+ /**
31
+ * The file this item edits; absent means the call's top-level `file`. One call may therefore
32
+ * cross files: each item resolves its anchors and its lease against the file it names.
33
+ */
34
+ file?: string;
35
+ };
36
+ type EditMode = "general" | "literal";
37
+ /**
38
+ * Internal normalized vocabulary owned by the admission boundary (ticket-01, renamed ticket-04).
39
+ * The wire never names these shapes: `normReq` builds them once from the validated payload and
40
+ * everything downstream consumes the union — the engine switches on `payload.kind` exhaustively.
41
+ */
42
+ export type Placement = "in-place" | "before" | "after";
43
+ /**
44
+ * @public Intentional surface (knip): the tuple type is the wire/engine
45
+ * vocabulary check, so the binding is never imported — its reason to exist
46
+ * is the typecheck failure it produces when the unions drift.
47
+ */
48
+ export declare const _placementVocabularyAgreement: [
49
+ Placement extends EditPlacement ? true : never,
50
+ EditPlacement extends Placement ? true : never
51
+ ];
52
+ /** The anchor pair an item targets — placement and payload are relative to its resolved span. */
53
+ type AnchorSpan = {
54
+ anchor_from: string;
55
+ anchor_to: string;
56
+ };
57
+ export type DesiredContent = {
58
+ kind: "literal";
59
+ text: string;
60
+ } | {
61
+ kind: "reference";
62
+ span: SpanRef;
63
+ mode: "copy" | "cut";
64
+ } | {
65
+ kind: "empty";
66
+ };
67
+ export type NormalizedEditItem = {
68
+ target: AnchorSpan;
69
+ at: Placement;
70
+ payload: DesiredContent;
71
+ /** The file this item edits, when the wire named one; absent means the request's `file`. */
72
+ file?: string;
73
+ };
74
+ export type NormalizedEditRequest = {
75
+ file: string;
76
+ edits: NormalizedEditItem[];
77
+ mode?: EditMode;
78
+ };
79
+ /** The wire-folded request (the admission analyzer's view): canonical keys, pre-union. */
80
+ type PreAdmissionRequest = {
81
+ file: string;
82
+ edits: EditItem[];
83
+ mode?: EditMode;
84
+ };
85
+ type NormalizedPayload = NormalizedEditRequest & {
86
+ readonly [normalizedEdit]: true;
87
+ };
88
+ type RawPayload = string | number | boolean | null | undefined | Record<string, unknown>;
89
+ export type NormReqResult = NormalizedPayload | RawPayload;
90
+ export declare const anchorFromSchema: Type.TString;
91
+ export declare const anchorToSchema: Type.TString;
92
+ export declare const editFileSchema: Type.TString;
93
+ export declare const editModeSchema: Type.TUnion<[Type.TLiteral<"general">, Type.TLiteral<"literal">]>;
94
+ export declare const editItemSchema: Type.TObject<{
95
+ anchor_from: Type.TString;
96
+ anchor_to: Type.TString;
97
+ at: Type.TOptional<Type.TUnion<[Type.TUnion<[Type.TLiteral<"in-place">, Type.TLiteral<"before">, Type.TLiteral<"after">]>, Type.TNull]>>;
98
+ file: Type.TOptional<Type.TUnion<[Type.TString, Type.TNull]>>;
99
+ text: Type.TOptional<Type.TUnion<[Type.TString, Type.TNull]>>;
100
+ text_ref: Type.TOptional<Type.TUnion<[Type.TObject<{
101
+ anchor_from: Type.TString;
102
+ anchor_to: Type.TString;
103
+ file: Type.TOptional<Type.TUnion<[Type.TString, Type.TNull]>>;
104
+ mode: Type.TUnion<[Type.TLiteral<"copy">, Type.TLiteral<"cut">]>;
105
+ }>, Type.TNull]>>;
106
+ }>;
107
+ export declare const editToolSchema: Type.TObject<{
108
+ file: Type.TString;
109
+ edits: Type.TArray<Type.TObject<{
110
+ anchor_from: Type.TString;
111
+ anchor_to: Type.TString;
112
+ at: Type.TOptional<Type.TUnion<[Type.TUnion<[Type.TLiteral<"in-place">, Type.TLiteral<"before">, Type.TLiteral<"after">]>, Type.TNull]>>;
113
+ file: Type.TOptional<Type.TUnion<[Type.TString, Type.TNull]>>;
114
+ text: Type.TOptional<Type.TUnion<[Type.TString, Type.TNull]>>;
115
+ text_ref: Type.TOptional<Type.TUnion<[Type.TObject<{
116
+ anchor_from: Type.TString;
117
+ anchor_to: Type.TString;
118
+ file: Type.TOptional<Type.TUnion<[Type.TString, Type.TNull]>>;
119
+ mode: Type.TUnion<[Type.TLiteral<"copy">, Type.TLiteral<"cut">]>;
120
+ }>, Type.TNull]>>;
121
+ }>>;
122
+ mode: Type.TOptional<Type.TUnion<[Type.TUnion<[Type.TLiteral<"general">, Type.TLiteral<"literal">]>, Type.TNull]>>;
123
+ }>;
124
+ export declare const EDIT_DESCRIPTION: string;
125
+ export declare const EDIT_SNIPPET: string;
126
+ export declare const EDIT_GUIDELINES: string[];
127
+ export declare function editRequestFrom(input: unknown): PreAdmissionRequest | undefined;
128
+ export declare function normReq(input: unknown): NormReqResult;
129
+ export declare function prepareEditArguments(args: unknown): Record<string, unknown>;
130
+ export declare function getPreviewInput(args: unknown): {
131
+ file: string;
132
+ edits: EditItem[];
133
+ } | null;
134
+ export declare function assertReq(request: unknown): asserts request is NormalizedEditRequest;
135
+ export {};
@@ -14,13 +14,7 @@ export interface FileSnapshotContext {
14
14
  export declare function buildRangeServeRows(startLine: number, endLine: number, fileHashes: string[]): ServedRow[];
15
15
  export declare function denseServeRows(hashes: readonly string[]): ServedRow[];
16
16
  export declare function fmtServedRows(rows: ServedRow[], fileLines: string[]): string;
17
- export declare function paginationHint(nextOffset: number, more: number): string;
18
17
  export declare function servedPositionsOf(served: (string | null)[], hash: string): number[];
19
- /** Builds the reject-and-serve served rows + rendered block for a current on-disk range. */
20
- export declare function buildRangeServeBlock(startLine: number, endLine: number, fileHashes: string[], fileLines: string[]): {
21
- servedRows: ServedRow[];
22
- rendered: string;
23
- };
24
18
  /**
25
19
  * Builds an `[E_TARGET_LOST]` rejection for a retired leased identity whose range cannot be
26
20
  * identified (spec stale-identity-reject-and-serve D1/D6, ADR-0018 decisions 1-2). The payload
@@ -25,23 +25,7 @@ export interface UndoRecord {
25
25
  * `null` is a row from before this column, or an ordinary single-file edit.
26
26
  */
27
27
  rawPre?: string | null;
28
- /**
29
- * The lifecycle action this row reverses. Absent/null is an ordinary edit (back-compatible).
30
- * add = the file did not exist before; delete = the file does not exist after; move = a pure
31
- * rename with an optional content edit.
32
- */
33
- lifecycle?: UndoLifecycle | null;
34
- /** Move: the absolute path the file had before the transaction. */
35
- fromPath?: string | null;
36
- /** Move: the absolute path the file has after the transaction. */
37
- toPath?: string | null;
38
- /** The RAW post-transaction text when the commit wrote non-canonical bytes (pure move). */
39
- rawPost?: string | null;
40
- /** Add: the directories the add created, outermost first, JSON-encoded in the column. */
41
- createdDirs?: readonly string[] | null;
42
28
  }
43
- /** The lifecycle action one undo row reverses. */
44
- export type UndoLifecycle = "edit" | "add" | "delete" | "move";
45
29
  export type CutIntentDirection = "revert" | null;
46
30
  export interface CutIntent {
47
31
  txnId: string;
@@ -49,7 +33,7 @@ export interface CutIntent {
49
33
  direction: CutIntentDirection;
50
34
  }
51
35
  export interface UndoStmts {
52
- undoUpsert: (path: string, content: string, bom: string, ending: string, hashes: string, resultContent: string, snapshotHash: string | null, transactionId: string | null, rawPre: string | null, lifecycle: string | null, fromPath: string | null, toPath: string | null, rawPost: string | null, createdDirs: string | null, anchorGeneration: number, updatedAt: number) => void;
36
+ undoUpsert: (path: string, content: string, bom: string, ending: string, hashes: string, resultContent: string, snapshotHash: string | null, transactionId: string | null, rawPre: string | null, anchorGeneration: number, updatedAt: number) => void;
53
37
  undoGet: (path: string) => Record<string, unknown> | undefined;
54
38
  undoDelete: (path: string) => void;
55
39
  undoGetTransaction: (transactionId: string) => Record<string, unknown>[];
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "dsh-hashline-edit",
3
- "version": "0.2.0",
4
- "description": "Hash-anchored read/apply_patch tools for DeepSeek Harness (dsh). Every line gets a unique 4-char hash (A-Za-z0-9) that stays stable across edits; apply_patch is the only file-mutation entry point and commits one patch batch per transaction. Undo persists across restarts.",
3
+ "version": "0.2.2",
4
+ "description": "Hash-anchored read/edit/undo_last_edit tools for DeepSeek Harness (dsh). Every line gets a unique 3- or 4-char hash (A-Za-z0-9, configurable) that stays stable across edits; stale or ambiguous anchors are rejected, never fuzzy-matched. Undo persists across restarts.",
5
5
  "keywords": [
6
6
  "anchors",
7
- "apply-patch",
8
7
  "coding-agent",
9
8
  "deepseek-harness",
10
9
  "dsh",
11
10
  "dsh-plugin",
11
+ "edit",
12
12
  "file-editing",
13
13
  "hash",
14
14
  "hash-anchored",
@@ -56,8 +56,17 @@
56
56
  "format:fix": "oxfmt .",
57
57
  "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.dsh.json",
58
58
  "build": "node scripts/build-dsh.mjs",
59
+ "build:pi": "node scripts/build-dist.mjs",
59
60
  "prepublishOnly": "node scripts/build-dsh.mjs && pnpm run typecheck && pnpm run lint && pnpm run test:win",
60
61
  "canon:audit": "node scripts/canon-churn-audit.mjs",
62
+ "prompts:show": "node scripts/served-prompts.mjs",
63
+ "replay:read-expansion": "node scripts/read-expansion-replay.mjs",
64
+ "test:runtime": "node scripts/runtime-edge-test.mjs",
65
+ "verify:dist": "node scripts/verify-dist.mjs",
66
+ "verify:dist:mutations": "node scripts/verify-dist-mutations.mjs",
67
+ "measure:import": "node scripts/measure-import.mjs --max-ratio 0.75",
68
+ "smoke:install": "node scripts/install-smoke.mjs",
69
+ "prepare": "node scripts/prepare.mjs",
61
70
  "prepack": "node scripts/build-dsh.mjs",
62
71
  "audit": "pnpm audit --audit-level high --registry https://registry.npmjs.org/",
63
72
  "verify": "pnpm run lint && pnpm run format && pnpm run typecheck && pnpm run test:win:coverage"
@@ -65,6 +74,7 @@
65
74
  "dependencies": {
66
75
  "diff": "^8.0.2",
67
76
  "file-type": "^21.3.0",
77
+ "typebox": "^1.3.27",
68
78
  "unbash": "5.0.0",
69
79
  "xxhash-wasm": "^1.1.0"
70
80
  },
@@ -72,12 +82,14 @@
72
82
  "@babel/parser": "7.29.8",
73
83
  "@commitlint/cli": "^21.2.2",
74
84
  "@commitlint/config-conventional": "^21.2.2",
75
- "@deepseek-ai/cordis": "4.0.6-alpha.2",
76
- "@deepseek-ai/dsh-agent": "0.2.2-alpha.3",
77
- "@deepseek-ai/dsh-fs": "0.2.2-alpha.3",
78
- "@deepseek-ai/dsh-home-paths": "0.2.2-alpha.3",
79
- "@deepseek-ai/dsh-sandbox": "0.2.2-alpha.3",
80
- "@deepseek-ai/dsh-tools": "0.2.2-alpha.3",
85
+ "@deepseek-ai/cordis": "^4.0.5",
86
+ "@deepseek-ai/dsh-agent": "0.2.1-rc.2",
87
+ "@deepseek-ai/dsh-fs": "0.2.1-rc.2",
88
+ "@deepseek-ai/dsh-home-paths": "0.2.1-rc.2",
89
+ "@deepseek-ai/dsh-sandbox": "0.2.1-rc.2",
90
+ "@deepseek-ai/dsh-tools": "0.2.1-rc.2",
91
+ "@earendil-works/pi-coding-agent": "^0.84.0",
92
+ "@earendil-works/pi-tui": "*",
81
93
  "@semantic-release/git": "^11.0.1",
82
94
  "@semantic-release/github": "^12.0.9",
83
95
  "@semantic-release/npm": "^13.1.5",
@@ -97,7 +109,6 @@
97
109
  "web-tree-sitter": "0.27.1"
98
110
  },
99
111
  "engines": {
100
- "dsh": ">=0.2.2-alpha.3 <0.2.3-0",
101
112
  "node": ">=24.0.0"
102
113
  },
103
114
  "packageManager": "pnpm@12.0.0",
@@ -0,0 +1,10 @@
1
+ - edit: an `anchor` is the bare {{HASH_LEN}}-char hash (for example "{{ANCHOR_EXAMPLE}}"). A `HASH│content` line (for example `{{ANCHOR_EXAMPLE}}│ pass`) is a served row, and `│` separates the two. Copy only the {{HASH_LEN}} chars before `│` into `anchor_from`/`anchor_to`.
2
+ - edit: give each item two anchors and exactly one payload. `file` is the text file. One item is one edit. Several items batch to that one file. Choose one payload field: `text` or `text_ref`. `at` is optional. A null in an optional field (`text`, `text_ref`, `at`, the top-level `mode`, `text_ref.file`) reads as absent. A null in a required field (`file`, `edits`, any anchor, `text_ref.mode`) is refused.
3
+ - edit: `anchor_from`/`anchor_to` bound an inclusive range. In-place replaces both boundary lines. `before`/`after` insert at the boundary instead. Out-of-band writes (bash, scripts, formatters) bypass serve recording. When a served line changes, the refusal serves the current rows. Copy fresh anchors from it.
4
+ - edit: `text` is plain file content. Join lines with `\n`. Use `""` to delete the range. The tool refuses a line that copies a served hash echo. `text` is verbatim, so include the indentation you want.
5
+ - edit: place the payload with `at`. `"in-place"` (default) rewrites the range. `"before"`/`"after"` insert at its boundary and need a single-line resolved target. `text: ""` with before/after writes nothing (noop).
6
+ - edit: `text_ref` `{anchor_from, anchor_to, mode}` writes the bytes of a served span into the target. `mode: "copy"` keeps the source. `mode: "cut"` removes those lines from the source. When `file` names another served file, both modes apply to that file's rows. A `cut` then removes the span there in the same call. `mode` is required. Prefer `text_ref` to reproduce served bytes exactly or to move them. Use `text` for content you author.
7
+ - edit: an anchor is bound to the file that served it. The lease is (session, file, anchor). Copy `anchor_from`/`anchor_to` only from the served rows of the file the payload names. That is this file by default, or `text_ref.file` when it names another file.
8
+ - edit: after success the diff serves fresh `HASH│content` rows: context rows and `+` rows. Copy new anchors from the diff for your next call. Verify that the diff matches your intended mutation.
9
+ - edit: a `[MODEL] [W_*]` line in `content` is informational. The mutation was applied. A `[MODEL] [E_*]` line is your retry instruction or a rejection: follow it from the message alone. A `[MODEL]` line that presents `Current range (fresh read):` shows the current rows. Decide from them. A dimmed `[USER]` line in `details` is human info.
10
+ - edit: batch independent ranges in one `edits` array. Inside it, every edit validates BEFORE the first rename, and any failure there writes nothing. A foreign `cut` writes its files in a fixed order, and a defeated rollback restores captured bytes or is repaired on the next run.
@@ -0,0 +1 @@
1
+ Edit a file range via `edit`: `{"file":file,"edits":[{"anchor_from":a,"anchor_to":b,"text":text}]}` — anchors are bare {{HASH_LEN}}-char hashes copied from served `HASH│content` from rows this file served this session. One payload per item: `text` is bare content (`""` deletes) or `text_ref` writes a served span (`"copy"` keeps source, `"cut"` removes it from this file or `file`). `at`: "in-place" (default), "before", "after". Chain from diff anchors.
@@ -0,0 +1 @@
1
+ Edit a range of lines in a text file via `edit`: `{ "file": file, "edits": [{ "anchor_from": a, "anchor_to": b, "text": text }, ...] }` (one file per call, or one per item `file`). Use rows this session served for the file: `read`, a diff, or a bash view. `anchor_from`/`anchor_to` are bare {{HASH_LEN}}-char HASH anchors. Copy the {{HASH_LEN}} chars before `│` in this file's served rows (lease (session, file, anchor)). One payload per item: `text` (`\n` joins lines, `""` deletes) or `text_ref` `{anchor_from, anchor_to, mode (required), file?}`, the bytes of a served span (`mode` `"copy"` keeps it, `"cut"` removes it). `file` names another served file. `at`: "in-place" (default), "before", "after". A null optional field reads as absent. `[MODEL] [E_*]` is your retry instruction. `[W_*]` is informational.
@@ -1,6 +1,6 @@
1
- - read: call it only for information the tool never served you, such as content past the auto-read diff. A bash view or an auto-read already served the file, so apply_patch from those rows.
2
- - read: when you need several blocks or both boundaries of one replacement, put every range in ONE call. Use `{ file, windows: [{ offset, limit }] }`. Each window is served in that turn, so its anchors work in the same apply_patch call. A window can also be `{ around_anchor, radius }` to centre on a line this session already served. An anchor outside this session's served rows is omitted with an inline `[Window warning: ...]`.
1
+ - read: call it only for information the tool never served you, such as content past the auto-read diff. A bash view or an auto-read already served the file, so edit from those rows.
2
+ - read: when you need several blocks or both boundaries of one replacement, put every range in ONE call. Use `{ file, windows: [{ offset, limit }] }`. Each window is served in that turn, so its anchors work in the same edit. A window can also be `{ around_anchor, radius }` to centre on a line this session already served. An anchor outside this session's served rows is omitted with an inline `[Window warning: ...]`.
3
3
  - read: a requested window of 61 lines or fewer can widen to its enclosing block; overlapping or adjacent windows fold before this test. Caps are 80 lines for code and Markdown, 40 for plain text. Widened rows begin with `[widened <requested> -> <served>, <label>]`; above three requested windows the note is `[widened N windows: <ranges>]`. An over-cap Markdown section clamps and names `(section <extent>)`; over-cap code or plain text refuses with no note. `mode: "verbatim"` widens too, with no anchors.
4
- - read: when you will not apply_patch the file, pass `mode: "verbatim"` for plain content with no anchors. Examples are a config value, docs, a README, or skill content (a SKILL.md or any file in its directory). Omit `mode` when you intend to use the anchors with apply_patch, so the rows carry anchors.
5
- - read: send one file per call as `{ file, windows?, mode? }`, the same way `apply_patch` does. The single top-level `mode` applies to the file. To read several files, make parallel `read` calls.
4
+ - read: when you will not edit the file, pass `mode: "verbatim"` for plain content with no anchors. Examples are a config value, docs, a README, or skill content (a SKILL.md or any file in its directory). Omit `mode` when you intend to edit, so the rows carry anchors.
5
+ - read: send one file per call as `{ file, windows?, mode? }`, the same way `edit` does. The single top-level `mode` applies to the file. To read several files, make parallel `read` calls.
6
6
  - read: PNG/JPG images return as image attachments for multimodal models. Request one with `file` like any other file.
package/prompts/read.md CHANGED
@@ -1 +1 @@
1
- Read one text file via `file`, with optional `windows` and top-level `mode`. To read several files, make parallel `read` calls. Served mode (default) returns each line as HASH│content with a 4-char alphanumeric hash. That hash is the `apply_patch` identity anchor, so copy only the 4 chars before `│`. Pass `mode: "verbatim"` for plain text with no anchors when you will not apply_patch. `mode: "verbatim"` is not capped by line budget. Name ranges with `windows`: `[{ offset, limit }]` (offset is 1-indexed, limit is a line count, maximum 16 windows) or `[{ around_anchor: "a1b2", radius: 10 }]` for a line this session served. When a page stops short, continue from the next unshown line with `windows: [{ offset, limit }]`. A requested window of 61 lines or fewer can widen to its enclosing block before serving; overlapping or adjacent windows fold before this test. Caps are 80 lines for code and Markdown, 40 for plain text. A widened page begins with `[widened <requested> -> <served>, <label>]`; more than three requested windows collapse to `[widened N windows: <ranges>]`. An over-cap Markdown section clamps to a cap-sized window and names `(section <extent>)`; an over-cap code or plain-text block refuses, serving the requested window. `mode: "verbatim"` widens the same way but adds no anchors. Inline `[...]` notices describe the served result, not file content. Empty files return `HASH│` (use an apply_patch identity hunk to insert). PNG/JPG/GIF/WebP return as image attachments for multimodal models. Binary and directory files are rejected. Hashes are file-scoped: an anchor is bound to the file it was served from.
1
+ Read one text file via `file`, with optional `windows` and top-level `mode`. To read several files, make parallel `read` calls. Served mode (default) returns each line as HASH│content with a {{HASH_LEN}}-char alphanumeric hash. That hash is the `edit` anchor, so copy only the {{HASH_LEN}} chars before `│`. Pass `mode: "verbatim"` for plain text with no anchors when you will not edit. `mode: "verbatim"` is not capped by line budget. Name ranges with `windows`: `[{ offset, limit }]` (offset is 1-indexed, limit is a line count, maximum 16 windows) or `[{ around_anchor: "<anchor>", radius: 10 }]` for a line this session served. When a page stops short, continue from the next unshown line with `windows: [{ offset, limit }]`. A requested window of 61 lines or fewer can widen to its enclosing block before serving; overlapping or adjacent windows fold before this test. Caps are 80 lines for code and Markdown, 40 for plain text. A widened page begins with `[widened <requested> -> <served>, <label>]`; more than three requested windows collapse to `[widened N windows: <ranges>]`. An over-cap Markdown section clamps to a cap-sized window and names `(section <extent>)`; an over-cap code or plain-text block refuses, serving the requested window. `mode: "verbatim"` widens the same way but adds no anchors. Inline `[...]` notices describe the served result, not file content. Empty files return `HASH│` (edit to insert). PNG/JPG/GIF/WebP return as image attachments for multimodal models. Binary and directory files are rejected. Hashes are file-scoped: an anchor is bound to the file it was served from.
@@ -1,2 +1,2 @@
1
- - undo_last_edit: reverts the most recent `apply_patch` batch that touched the file at `path`, and every member of that batch: text hunks plus file lifecycle actions (add/delete/move). The exception is a foreign `cut`: undo then restores the source file too. The file must still hold the edited bytes. An out-of-band write makes the call fail with `E_UNDO_STALE`, and the file stays byte-identical. Call it as soon as a result is bad.
2
- - undo_last_edit: a `write` to the file, or a later `apply_patch` batch, replaces the undo entry. A read, a bash view or an out-of-band write leaves the entry in place. An apply_patch result is bad when its diff shows `-HASH│` rows for lines you meant to keep. Examples are a closing brace, an import, or a declaration.
1
+ - undo_last_edit: reverts the most recent `edit` call on the file at `path`, and every item of its `edits`. Its scope is the edited file. The exception is an edit that was a foreign `cut`: undo then restores the source file too. The file must still hold the edited bytes. An out-of-band write makes the call fail with `E_UNDO_STALE`, and the file stays byte-identical. Call it as soon as a result is bad.
2
+ - undo_last_edit: a `write` to the file, or a later `edit`, replaces the undo entry. A read, a bash view or an out-of-band write leaves the entry in place. An edit is bad when its post-edit diff shows `-HASH│` rows for lines you meant to keep. Examples are a closing brace, an import, or a declaration.
@@ -1 +1 @@
1
- Undo the last `apply_patch` batch on the file at `path`
1
+ Undo the last edit on the file at `path`
@@ -1 +1 @@
1
- Undo the last `apply_patch` batch that touched the file at `path`, reverting the whole batch — every text hunk plus file lifecycle actions (add/delete/move) — to its previous state. Use when an `apply_patch` produced incorrect results (e.g., wrong content, duplicated lines, broken syntax). The file must be unchanged since that batch.
1
+ Undo the last edit on the file at `path`, reverting it to its previous state. Use when an edit produced incorrect results (e.g., wrong content, duplicated lines, broken syntax). The file must be unchanged since that edit.