@introspection-ai/recipe-documents-docx 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -2
- package/dist/index.d.ts +44 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +77 -0
- package/dist/index.js.map +1 -0
- package/dist/python.d.ts +26 -0
- package/dist/python.d.ts.map +1 -0
- package/dist/python.js +52 -0
- package/dist/python.js.map +1 -0
- package/package.json +48 -3
- package/profiles/legal-redlining-en-us/SKILL.md +55 -0
- package/python/docx_helper.py +55 -0
- package/python/pyproject.toml +10 -0
- package/python/recipe_documents_docx/LICENSE-redline-bench +28 -0
- package/python/recipe_documents_docx/__init__.py +1 -0
- package/python/recipe_documents_docx/anchors.py +186 -0
- package/python/recipe_documents_docx/model.py +279 -0
- package/python/recipe_documents_docx/ops.py +939 -0
- package/python/uv.lock +119 -0
package/README.md
CHANGED
|
@@ -1,3 +1,57 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @introspection-ai/recipe-documents-docx
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The Word (`.docx`) format for the Recipes [`documents` tool](../../../docs/documents.md):
|
|
4
|
+
`read`, `propose_edits`, `comment`, `respond` and `remove_section`, on paper-docx.
|
|
5
|
+
`render` is not registered.
|
|
6
|
+
|
|
7
|
+
## With and without a profile
|
|
8
|
+
|
|
9
|
+
The tool is the same either way: the same commands, the same checks, and the original
|
|
10
|
+
is never changed. A profile only adds guidance the agent reads.
|
|
11
|
+
|
|
12
|
+
**Without a profile** — any Word work: editing a policy, filling a template, marking up
|
|
13
|
+
a memo, answering comments.
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{ "pi": { "documents": [{ "format": "docx" }] } }
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The agent gets the core `documents` skill: which command fits which change, how to quote
|
|
20
|
+
text so an edit lands, and what to do when an edit fails. It says nothing about any one
|
|
21
|
+
kind of work, so the Recipe's own skills decide how changes should read.
|
|
22
|
+
|
|
23
|
+
**With `legal-redlining-en-US`** — contract redlines, and responses to the other side's
|
|
24
|
+
redline.
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{ "pi": { "documents": [{ "format": "docx", "profile": "legal-redlining-en-US" }] } }
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The agent also gets `profiles/legal-redlining-en-us/SKILL.md`, which covers:
|
|
31
|
+
|
|
32
|
+
- planning a first markup by what moves a right, an obligation or a risk;
|
|
33
|
+
- making the changes in `revise` batches, one rationale comment per change;
|
|
34
|
+
- removing a clause with `remove_section`, leaving `Reserved.` so numbering still lines up;
|
|
35
|
+
- answering the other side's changes with counters and replies, so the discussion stays
|
|
36
|
+
visible, rather than accepting or rejecting;
|
|
37
|
+
- writing each rationale as the reason, in your side's voice, never narrating the edit.
|
|
38
|
+
|
|
39
|
+
The profile is generic legal practice. A firm's own positions, playbook and comment
|
|
40
|
+
style belong in the Recipe's skills, beside it.
|
|
41
|
+
|
|
42
|
+
**Your own profile** — for another kind of work with its own way of using the tool,
|
|
43
|
+
write a profile in the Recipe: a folder holding a `SKILL.md` whose frontmatter `name` is
|
|
44
|
+
the folder's name, and point `profile` at it.
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{ "pi": { "documents": [{ "format": "docx", "profile": "./profiles/house-style-en-gb" }] } }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
[Write your own profile](../../../docs/documents.md#write-your-own-profile) has the
|
|
51
|
+
contract, what a profile should say, and a complete example.
|
|
52
|
+
|
|
53
|
+
The helpers in `python/` run with the Recipe's `pi.runtime.python` environment, which must
|
|
54
|
+
provide `paper-docx==0.2.0`. `python/uv.lock` pins what the tests run on.
|
|
55
|
+
|
|
56
|
+
Licences: the anchoring and section removal follow RedlineBench's redline engine (MIT,
|
|
57
|
+
Crosby Legal), see `python/recipe_documents_docx/LICENSE-redline-bench`. The rest is Apache-2.0.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type DocumentAdapter, type DocumentAdapterContext, type DocumentCapabilities, type DocumentCommentInput, type DocumentProposeEditsInput, type DocumentReadInput, type DocumentRemoveSectionInput, type DocumentRespondInput, type DocumentReviseInput, type DocumentResult } from "@introspection-ai/recipes/documents";
|
|
2
|
+
export { pythonExecutable, runHelper } from "./python.js";
|
|
3
|
+
/** The helper entry point shipped in this package. */
|
|
4
|
+
export declare const DOCX_HELPER: string;
|
|
5
|
+
/**
|
|
6
|
+
* What Word supports. `render` is false: page images need a layout engine
|
|
7
|
+
* (LibreOffice or Word), which the runtime image does not carry, so the
|
|
8
|
+
* command is absent for .docx rather than failing.
|
|
9
|
+
*/
|
|
10
|
+
export declare const DOCX_DOCUMENT_CAPABILITIES: DocumentCapabilities;
|
|
11
|
+
export declare class DocxDocumentAdapter implements DocumentAdapter {
|
|
12
|
+
private readonly env;
|
|
13
|
+
private readonly script;
|
|
14
|
+
readonly format = "docx";
|
|
15
|
+
readonly extensions: readonly [".docx"];
|
|
16
|
+
readonly capabilities: DocumentCapabilities;
|
|
17
|
+
constructor(env?: NodeJS.ProcessEnv, script?: string);
|
|
18
|
+
private call;
|
|
19
|
+
read(ctx: DocumentAdapterContext, input: DocumentReadInput): Promise<DocumentResult>;
|
|
20
|
+
revise(ctx: DocumentAdapterContext, input: DocumentReviseInput): Promise<DocumentResult>;
|
|
21
|
+
proposeEdits(ctx: DocumentAdapterContext, input: DocumentProposeEditsInput): Promise<DocumentResult>;
|
|
22
|
+
comment(ctx: DocumentAdapterContext, input: DocumentCommentInput): Promise<DocumentResult>;
|
|
23
|
+
respond(ctx: DocumentAdapterContext, input: DocumentRespondInput): Promise<DocumentResult>;
|
|
24
|
+
removeSection(ctx: DocumentAdapterContext, input: DocumentRemoveSectionInput): Promise<DocumentResult>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Word as a document format.
|
|
28
|
+
*
|
|
29
|
+
* ```json
|
|
30
|
+
* { "pi": { "documents": [{ "format": "docx" }] } }
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* ```yaml
|
|
34
|
+
* tools: [documents]
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
/**
|
|
38
|
+
* Contract redlining in US English: how to plan a first markup and answer the
|
|
39
|
+
* other side's redline. A Recipe selects it with `"profile": "legal-redlining-en-US"`.
|
|
40
|
+
*/
|
|
41
|
+
export declare const LEGAL_REDLINING_EN_US: string;
|
|
42
|
+
export declare const docxDocumentFormatModule: import("@introspection-ai/recipes/documents").RecipeDocumentFormatModule;
|
|
43
|
+
export default docxDocumentFormatModule;
|
|
44
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,iBAAiB,EACtB,KAAK,0BAA0B,EAC/B,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACpB,MAAM,qCAAqC,CAAC;AAI7C,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE1D,sDAAsD;AACtD,eAAO,MAAM,WAAW,QAAsE,CAAC;AAE/F;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,EAAE,oBAQxC,CAAC;AAEF,qBAAa,mBAAoB,YAAW,eAAe;IAK7C,OAAO,CAAC,QAAQ,CAAC,GAAG;IAAmC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJ1F,QAAQ,CAAC,MAAM,UAAU;IACzB,QAAQ,CAAC,UAAU,YAAI,OAAO,EAAW;IACzC,QAAQ,CAAC,YAAY,uBAA8B;IAEnD,YAA6B,GAAG,GAAE,MAAM,CAAC,UAAwB,EAAmB,MAAM,SAAc,EAAI;IAE5G,OAAO,CAAC,IAAI;IAIZ,IAAI,CAAC,GAAG,EAAE,sBAAsB,EAAE,KAAK,EAAE,iBAAiB,2BAEzD;IAED,MAAM,CAAC,GAAG,EAAE,sBAAsB,EAAE,KAAK,EAAE,mBAAmB,2BAE7D;IAED,YAAY,CAAC,GAAG,EAAE,sBAAsB,EAAE,KAAK,EAAE,yBAAyB,2BAEzE;IAED,OAAO,CAAC,GAAG,EAAE,sBAAsB,EAAE,KAAK,EAAE,oBAAoB,2BAE/D;IAED,OAAO,CAAC,GAAG,EAAE,sBAAsB,EAAE,KAAK,EAAE,oBAAoB,2BAE/D;IAED,aAAa,CAAC,GAAG,EAAE,sBAAsB,EAAE,KAAK,EAAE,0BAA0B,2BAE3E;CACF;AAED;;;;;;;;;;GAUG;AACH;;;GAGG;AACH,eAAO,MAAM,qBAAqB,QAAwF,CAAC;AAE3H,eAAO,MAAM,wBAAwB,0EAMnC,CAAC;eAEY,wBAAwB"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { createDocumentFormatModule, } from "@introspection-ai/recipes/documents";
|
|
3
|
+
import { runHelper } from "./python.js";
|
|
4
|
+
export { pythonExecutable, runHelper } from "./python.js";
|
|
5
|
+
/** The helper entry point shipped in this package. */
|
|
6
|
+
export const DOCX_HELPER = fileURLToPath(new URL("../python/docx_helper.py", import.meta.url));
|
|
7
|
+
/**
|
|
8
|
+
* What Word supports. `render` is false: page images need a layout engine
|
|
9
|
+
* (LibreOffice or Word), which the runtime image does not carry, so the
|
|
10
|
+
* command is absent for .docx rather than failing.
|
|
11
|
+
*/
|
|
12
|
+
export const DOCX_DOCUMENT_CAPABILITIES = {
|
|
13
|
+
read: true,
|
|
14
|
+
revise: true,
|
|
15
|
+
proposeEdits: true,
|
|
16
|
+
comment: true,
|
|
17
|
+
respond: true,
|
|
18
|
+
removeSection: true,
|
|
19
|
+
render: false,
|
|
20
|
+
};
|
|
21
|
+
export class DocxDocumentAdapter {
|
|
22
|
+
env;
|
|
23
|
+
script;
|
|
24
|
+
format = "docx";
|
|
25
|
+
extensions = [".docx"];
|
|
26
|
+
capabilities = DOCX_DOCUMENT_CAPABILITIES;
|
|
27
|
+
constructor(env = process.env, script = DOCX_HELPER) {
|
|
28
|
+
this.env = env;
|
|
29
|
+
this.script = script;
|
|
30
|
+
}
|
|
31
|
+
call(ctx, command, input) {
|
|
32
|
+
return runHelper(this.script, { command, ...input }, { env: this.env, cwd: ctx.cwd, signal: ctx.signal });
|
|
33
|
+
}
|
|
34
|
+
read(ctx, input) {
|
|
35
|
+
return this.call(ctx, "read", input);
|
|
36
|
+
}
|
|
37
|
+
revise(ctx, input) {
|
|
38
|
+
return this.call(ctx, "revise", input);
|
|
39
|
+
}
|
|
40
|
+
proposeEdits(ctx, input) {
|
|
41
|
+
return this.call(ctx, "propose_edits", input);
|
|
42
|
+
}
|
|
43
|
+
comment(ctx, input) {
|
|
44
|
+
return this.call(ctx, "comment", input);
|
|
45
|
+
}
|
|
46
|
+
respond(ctx, input) {
|
|
47
|
+
return this.call(ctx, "respond", input);
|
|
48
|
+
}
|
|
49
|
+
removeSection(ctx, input) {
|
|
50
|
+
return this.call(ctx, "remove_section", input);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Word as a document format.
|
|
55
|
+
*
|
|
56
|
+
* ```json
|
|
57
|
+
* { "pi": { "documents": [{ "format": "docx" }] } }
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* ```yaml
|
|
61
|
+
* tools: [documents]
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
/**
|
|
65
|
+
* Contract redlining in US English: how to plan a first markup and answer the
|
|
66
|
+
* other side's redline. A Recipe selects it with `"profile": "legal-redlining-en-US"`.
|
|
67
|
+
*/
|
|
68
|
+
export const LEGAL_REDLINING_EN_US = fileURLToPath(new URL("../profiles/legal-redlining-en-us/SKILL.md", import.meta.url));
|
|
69
|
+
export const docxDocumentFormatModule = createDocumentFormatModule({
|
|
70
|
+
format: "docx",
|
|
71
|
+
extensions: [".docx"],
|
|
72
|
+
capabilities: DOCX_DOCUMENT_CAPABILITIES,
|
|
73
|
+
profiles: { "legal-redlining-en-US": LEGAL_REDLINING_EN_US },
|
|
74
|
+
createAdapter: ({ env }) => new DocxDocumentAdapter(env),
|
|
75
|
+
});
|
|
76
|
+
export default docxDocumentFormatModule;
|
|
77
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EACL,0BAA0B,GAW3B,MAAM,qCAAqC,CAAC;AAE7C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE1D,sDAAsD;AACtD,MAAM,CAAC,MAAM,WAAW,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,0BAA0B,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE/F;;;;GAIG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAyB;IAC9D,IAAI,EAAE,IAAI;IACV,MAAM,EAAE,IAAI;IACZ,YAAY,EAAE,IAAI;IAClB,OAAO,EAAE,IAAI;IACb,OAAO,EAAE,IAAI;IACb,aAAa,EAAE,IAAI;IACnB,MAAM,EAAE,KAAK;CACd,CAAC;AAEF,MAAM,OAAO,mBAAmB;IAKD,GAAG;IAAoD,MAAM;IAJjF,MAAM,GAAG,MAAM,CAAC;IAChB,UAAU,GAAG,CAAC,OAAO,CAAU,CAAC;IAChC,YAAY,GAAG,0BAA0B,CAAC;IAEnD,YAA6B,GAAG,GAAsB,OAAO,CAAC,GAAG,EAAmB,MAAM,GAAG,WAAW;mBAA3E,GAAG;sBAAoD,MAAM;IAAiB,CAAC;IAEpG,IAAI,CAAC,GAA2B,EAAE,OAAe,EAAE,KAAa;QACtE,OAAO,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,GAAG,KAAK,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5G,CAAC;IAED,IAAI,CAAC,GAA2B,EAAE,KAAwB;QACxD,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;IACvC,CAAC;IAED,MAAM,CAAC,GAA2B,EAAE,KAA0B;QAC5D,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;IACzC,CAAC;IAED,YAAY,CAAC,GAA2B,EAAE,KAAgC;QACxE,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,EAAE,KAAK,CAAC,CAAC;IAChD,CAAC;IAED,OAAO,CAAC,GAA2B,EAAE,KAA2B;QAC9D,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;IAC1C,CAAC;IAED,OAAO,CAAC,GAA2B,EAAE,KAA2B;QAC9D,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;IAC1C,CAAC;IAED,aAAa,CAAC,GAA2B,EAAE,KAAiC;QAC1E,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,gBAAgB,EAAE,KAAK,CAAC,CAAC;IACjD,CAAC;CACF;AAED;;;;;;;;;;GAUG;AACH;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,4CAA4C,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE3H,MAAM,CAAC,MAAM,wBAAwB,GAAG,0BAA0B,CAAC;IACjE,MAAM,EAAE,MAAM;IACd,UAAU,EAAE,CAAC,OAAO,CAAC;IACrB,YAAY,EAAE,0BAA0B;IACxC,QAAQ,EAAE,EAAE,uBAAuB,EAAE,qBAAqB,EAAE;IAC5D,aAAa,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,IAAI,mBAAmB,CAAC,GAAG,CAAC;CACzD,CAAC,CAAC;AAEH,eAAe,wBAAwB,CAAC"}
|
package/dist/python.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How the helpers run: the Recipe's own Python environment.
|
|
3
|
+
*
|
|
4
|
+
* The runtime activates `pi.runtime.python` as VIRTUAL_ENV, and that
|
|
5
|
+
* environment must provide paper-docx. A package-level Python environment
|
|
6
|
+
* is a follow-up; until then the Recipe declares the dependency.
|
|
7
|
+
* `RECIPE_DOCUMENTS_PYTHON` overrides the interpreter (tests, custom hosts).
|
|
8
|
+
*/
|
|
9
|
+
export declare function pythonExecutable(env?: NodeJS.ProcessEnv): string;
|
|
10
|
+
export interface HelperResult {
|
|
11
|
+
text: string;
|
|
12
|
+
details?: Record<string, unknown>;
|
|
13
|
+
images?: Array<{
|
|
14
|
+
label: string;
|
|
15
|
+
data: string;
|
|
16
|
+
mimeType: string;
|
|
17
|
+
}>;
|
|
18
|
+
}
|
|
19
|
+
/** Run one helper request: JSON on stdin, JSON on stdout. A helper error becomes the tool call's error. */
|
|
20
|
+
export declare function runHelper(script: string, request: Record<string, unknown>, options: {
|
|
21
|
+
env: NodeJS.ProcessEnv;
|
|
22
|
+
cwd: string;
|
|
23
|
+
signal?: AbortSignal;
|
|
24
|
+
timeoutMs?: number;
|
|
25
|
+
}): Promise<HelperResult>;
|
|
26
|
+
//# sourceMappingURL=python.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"python.d.ts","sourceRoot":"","sources":["../src/python.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAI7E;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACnE;AAED,2GAA2G;AAC3G,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GACzF,OAAO,CAAC,YAAY,CAAC,CAmCvB"}
|
package/dist/python.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* How the helpers run: the Recipe's own Python environment.
|
|
5
|
+
*
|
|
6
|
+
* The runtime activates `pi.runtime.python` as VIRTUAL_ENV, and that
|
|
7
|
+
* environment must provide paper-docx. A package-level Python environment
|
|
8
|
+
* is a follow-up; until then the Recipe declares the dependency.
|
|
9
|
+
* `RECIPE_DOCUMENTS_PYTHON` overrides the interpreter (tests, custom hosts).
|
|
10
|
+
*/
|
|
11
|
+
export function pythonExecutable(env = process.env) {
|
|
12
|
+
if (env.RECIPE_DOCUMENTS_PYTHON)
|
|
13
|
+
return env.RECIPE_DOCUMENTS_PYTHON;
|
|
14
|
+
if (env.VIRTUAL_ENV)
|
|
15
|
+
return resolve(env.VIRTUAL_ENV, "bin", "python");
|
|
16
|
+
return "python3";
|
|
17
|
+
}
|
|
18
|
+
/** Run one helper request: JSON on stdin, JSON on stdout. A helper error becomes the tool call's error. */
|
|
19
|
+
export function runHelper(script, request, options) {
|
|
20
|
+
const started = performance.now();
|
|
21
|
+
return new Promise((resolvePromise, reject) => {
|
|
22
|
+
const child = execFile(pythonExecutable(options.env), ["-I", script], {
|
|
23
|
+
cwd: options.cwd,
|
|
24
|
+
env: { ...options.env, PYTHONDONTWRITEBYTECODE: "1", PYTHONIOENCODING: "utf-8" },
|
|
25
|
+
timeout: options.timeoutMs ?? 300_000,
|
|
26
|
+
maxBuffer: 64 << 20,
|
|
27
|
+
signal: options.signal,
|
|
28
|
+
}, (error, stdout, stderr) => {
|
|
29
|
+
let output;
|
|
30
|
+
try {
|
|
31
|
+
output = JSON.parse(String(stdout || "null"));
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
output = undefined;
|
|
35
|
+
}
|
|
36
|
+
if (output && typeof output.error === "string")
|
|
37
|
+
return reject(new Error(output.error));
|
|
38
|
+
if (!output || typeof output.text !== "string") {
|
|
39
|
+
const detail = String(stderr || error?.message || "no output").slice(-600);
|
|
40
|
+
return reject(new Error(`document helper failed: ${detail}`));
|
|
41
|
+
}
|
|
42
|
+
const timing = (output.details?.timing ?? {});
|
|
43
|
+
output.details = {
|
|
44
|
+
...output.details,
|
|
45
|
+
timing: { ...timing, call_ms: Math.round(performance.now() - started) },
|
|
46
|
+
};
|
|
47
|
+
resolvePromise(output);
|
|
48
|
+
});
|
|
49
|
+
child.stdin?.end(JSON.stringify(request));
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=python.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"python.js","sourceRoot":"","sources":["../src/python.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAG,GAAsB,OAAO,CAAC,GAAG;IACnE,IAAI,GAAG,CAAC,uBAAuB;QAAE,OAAO,GAAG,CAAC,uBAAuB,CAAC;IACpE,IAAI,GAAG,CAAC,WAAW;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;IACtE,OAAO,SAAS,CAAC;AACnB,CAAC;AAQD,2GAA2G;AAC3G,MAAM,UAAU,SAAS,CACvB,MAAc,EACd,OAAgC,EAChC,OAA0F;IAE1F,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;IAClC,OAAO,IAAI,OAAO,CAAC,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE;QAC5C,MAAM,KAAK,GAAG,QAAQ,CACpB,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,EAC7B,CAAC,IAAI,EAAE,MAAM,CAAC,EACd;YACE,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,uBAAuB,EAAE,GAAG,EAAE,gBAAgB,EAAE,OAAO,EAAE;YAChF,OAAO,EAAE,OAAO,CAAC,SAAS,IAAI,OAAO;YACrC,SAAS,EAAE,EAAE,IAAI,EAAE;YACnB,MAAM,EAAE,OAAO,CAAC,MAAM;SACvB,EACD,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACxB,IAAI,MAAuD,CAAC;YAC5D,IAAI,CAAC;gBACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,CAAC,CAAC;YAChD,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,GAAG,SAAS,CAAC;YACrB,CAAC;YACD,IAAI,MAAM,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;gBAAE,OAAO,MAAM,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YACvF,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC/C,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,KAAK,EAAE,OAAO,IAAI,WAAW,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC;gBAC3E,OAAO,MAAM,CAAC,IAAI,KAAK,CAAC,2BAA2B,MAAM,EAAE,CAAC,CAAC,CAAC;YAChE,CAAC;YACD,MAAM,MAAM,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,IAAI,EAAE,CAA2B,CAAC;YACxE,MAAM,CAAC,OAAO,GAAG;gBACf,GAAG,MAAM,CAAC,OAAO;gBACjB,MAAM,EAAE,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,EAAE;aACxE,CAAC;YACF,cAAc,CAAC,MAAM,CAAC,CAAC;QACzB,CAAC,CACF,CAAC;QACF,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@introspection-ai/recipe-documents-docx",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Word (.docx) format for the Introspection Recipes documents tool: tracked edits, comments and responses on paper-docx.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=24"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"profiles",
|
|
13
|
+
"python/*.py",
|
|
14
|
+
"python/recipe_documents_docx/*.py",
|
|
15
|
+
"python/recipe_documents_docx/LICENSE-redline-bench",
|
|
16
|
+
"python/pyproject.toml",
|
|
17
|
+
"python/uv.lock",
|
|
18
|
+
"README.md",
|
|
19
|
+
"package.json"
|
|
20
|
+
],
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+ssh://git@github.com/introspection-org/recipes.git",
|
|
24
|
+
"directory": "packages/documents/docx"
|
|
25
|
+
},
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"import": "./dist/index.js",
|
|
30
|
+
"default": "./dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./package.json": "./package.json"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@introspection-ai/recipes": ">=0.37.0 <1.0.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@introspection-ai/recipes": ">=0.37.0",
|
|
39
|
+
"@types/node": "^26.1.2",
|
|
40
|
+
"typescript": "^7.0.2"
|
|
41
|
+
},
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public",
|
|
44
|
+
"tag": "latest"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsc -p tsconfig.json",
|
|
48
|
+
"typecheck": "tsc --noEmit -p tsconfig.typecheck.json",
|
|
49
|
+
"pack:check": "pnpm pack --dry-run --json | node ../../../scripts/check-npm-pack.mjs"
|
|
50
|
+
}
|
|
6
51
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: legal-redlining-en-us
|
|
3
|
+
description: How to use the documents tool for a contract redline, or a response to the other side's redline (profile legal-redlining-en-US). Read it with the documents skill before changing a contract.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Documents for legal redlining
|
|
7
|
+
|
|
8
|
+
This profile layers on the core `documents` skill. Every change is a tracked change with
|
|
9
|
+
its rationale as a margin comment, written for the other side's lawyer to read.
|
|
10
|
+
|
|
11
|
+
## A first markup
|
|
12
|
+
|
|
13
|
+
1. `read` the brief and the whole contract, every part.
|
|
14
|
+
2. Plan by priority before editing: what moves a right, an obligation or a risk. Leave
|
|
15
|
+
drafting preferences alone unless the brief asks for them.
|
|
16
|
+
3. Make the changes in one `revise` batch, or as few as possible, in document order. For
|
|
17
|
+
each paragraph give its `paragraph` id, its full revised `text` (copy the current text
|
|
18
|
+
from `read` and change only what must move) and the `rationale`.
|
|
19
|
+
4. Revise table cells by their ids (`p-105.r2c3`); a short rationale is fine for the cells
|
|
20
|
+
of a row that share a reason.
|
|
21
|
+
5. Use `propose_edits` for a small fix after a batch, such as one figure, quoted from a
|
|
22
|
+
fresh `read`.
|
|
23
|
+
6. Remove a whole numbered clause with `remove_section`: it strikes `from`..`through` and
|
|
24
|
+
leaves `Reserved.` after `keep` (the heading), so the numbering after it still lines
|
|
25
|
+
up. Strike a clause's words with `revise` only when the clause stays.
|
|
26
|
+
|
|
27
|
+
## A response turn
|
|
28
|
+
|
|
29
|
+
Tracked changes and comments in a file aren't always the other side's. Sort them by
|
|
30
|
+
author first: some can be your own side's earlier turn, or your own edits from this
|
|
31
|
+
session. Treat it as a response turn only when the request says the file came back from
|
|
32
|
+
the other side, or the authors show it did. Then, for the other side's markup:
|
|
33
|
+
|
|
34
|
+
- Leave their changes in place and layer yours on top, so they see what you did.
|
|
35
|
+
- `respond` with `counter` answers one of their changes with your own tracked change,
|
|
36
|
+
inside their language, with the rationale on its `thread` when the point has one.
|
|
37
|
+
Prefer it to rejecting: a counter keeps the discussion visible.
|
|
38
|
+
- Agree with `respond` `{action: "reply", thread, text: "Accepted."}`. Accept or reject
|
|
39
|
+
only when the request asks to resolve changes for good, such as cleaning a draft
|
|
40
|
+
before it goes out; those leave no trace.
|
|
41
|
+
- Changes the tool says it can't accept or reject here are for the lawyer to resolve in
|
|
42
|
+
Word: list them in the open decisions.
|
|
43
|
+
|
|
44
|
+
## Rationale
|
|
45
|
+
|
|
46
|
+
Write each rationale as the reason, in one or two sentences, in your side's voice: what
|
|
47
|
+
the change protects and why the original doesn't work for you. Never narrate the edit
|
|
48
|
+
("Changed thirty to sixty"); the tracked change already shows it.
|
|
49
|
+
|
|
50
|
+
## When an edit fails
|
|
51
|
+
|
|
52
|
+
Fix that edit from its reason and the closest text, and resubmit only it. An edit that
|
|
53
|
+
fails twice is skipped: list it under "Edits not applied" in the open decisions, and never
|
|
54
|
+
retry it by widening it to more text. Read the redline back once before you finish and
|
|
55
|
+
check every change has its rationale.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Entry point the Node adapter runs: one JSON request on stdin, one JSON result on stdout.
|
|
2
|
+
|
|
3
|
+
python -I docx_helper.py < request.json -> {"text": ..., "details": ...} or {"error": ...}
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
STARTED = time.perf_counter()
|
|
12
|
+
|
|
13
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def main() -> int:
|
|
17
|
+
try:
|
|
18
|
+
request = json.load(sys.stdin)
|
|
19
|
+
import docx
|
|
20
|
+
|
|
21
|
+
from recipe_documents_docx.ops import COMMANDS
|
|
22
|
+
|
|
23
|
+
imported = time.perf_counter()
|
|
24
|
+
opening = [0.0]
|
|
25
|
+
original_open = docx.Document
|
|
26
|
+
|
|
27
|
+
def timed_open(*args, **kwargs):
|
|
28
|
+
began = time.perf_counter()
|
|
29
|
+
try:
|
|
30
|
+
return original_open(*args, **kwargs)
|
|
31
|
+
finally:
|
|
32
|
+
opening[0] += time.perf_counter() - began
|
|
33
|
+
|
|
34
|
+
docx.Document = timed_open
|
|
35
|
+
command = COMMANDS.get(request.get("command"))
|
|
36
|
+
if command is None:
|
|
37
|
+
raise ValueError(f"the Word adapter has no command {request.get('command')!r}")
|
|
38
|
+
result = command(request)
|
|
39
|
+
result.setdefault("details", {})["timing"] = {
|
|
40
|
+
"import_ms": round((imported - STARTED) * 1000, 1),
|
|
41
|
+
"open_ms": round(opening[0] * 1000, 1),
|
|
42
|
+
"python_ms": round((time.perf_counter() - STARTED) * 1000, 1),
|
|
43
|
+
}
|
|
44
|
+
print(json.dumps(result, ensure_ascii=False))
|
|
45
|
+
return 0
|
|
46
|
+
except ModuleNotFoundError as error:
|
|
47
|
+
print(json.dumps({"error": f"This Recipe's Python has no {error.name}: the Word adapter needs paper-docx"}))
|
|
48
|
+
return 1
|
|
49
|
+
except Exception as error: # the tool reports it as the call's error
|
|
50
|
+
print(json.dumps({"error": f"{type(error).__name__}: {error}"}))
|
|
51
|
+
return 1
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
if __name__ == "__main__":
|
|
55
|
+
sys.exit(main())
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "recipe-documents-docx"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Python helpers the @introspection-ai/recipe-documents-docx adapter runs. For its tests; a Recipe brings its own environment with paper-docx."
|
|
5
|
+
requires-python = ">=3.12,<3.15"
|
|
6
|
+
dependencies = ["paper-docx==0.2.0"]
|
|
7
|
+
|
|
8
|
+
[tool.uv]
|
|
9
|
+
package = false
|
|
10
|
+
constraint-dependencies = ["python-docx<0"]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Crosby Legal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
The benchmark dataset (the contracts, grounding documents, rubrics, and golden
|
|
26
|
+
attorney redlines hosted at https://huggingface.co/datasets/crosbylegal/RedlineBench)
|
|
27
|
+
is licensed separately under Creative Commons Attribution 4.0 International
|
|
28
|
+
(CC-BY-4.0): https://creativecommons.org/licenses/by/4.0/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Word helpers for the Recipes `documents` tool, on paper-docx."""
|