@gillcash/necktie 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/.opencode/command/necktie-critique.md +5 -0
- package/.opencode/command/necktie-reverse.md +5 -0
- package/.opencode/command/necktie-review.md +5 -0
- package/.opencode/command/necktie.md +5 -0
- package/.opencode/plugins/necktie-frontmatter.cjs +12 -0
- package/.opencode/plugins/necktie.mjs +35 -0
- package/.qoder/rules/necktie.md +18 -0
- package/.qoder-plugin/plugin.json +13 -0
- package/AGENTS.md +18 -0
- package/LICENSE +22 -0
- package/NOTICE +7 -0
- package/README.es.md +84 -0
- package/README.ko.md +84 -0
- package/README.md +232 -0
- package/assets/logo-dark.png +0 -0
- package/assets/necktie-icon-pack-preview.png +0 -0
- package/commands/necktie-critique.toml +2 -0
- package/commands/necktie-reverse.toml +2 -0
- package/commands/necktie-review.toml +2 -0
- package/commands/necktie.toml +2 -0
- package/core/necktie-core.md +18 -0
- package/hooks/copilot-hooks.json +13 -0
- package/hooks/hooks.json +27 -0
- package/hooks/necktie-context.js +43 -0
- package/hooks/qoder-hooks.json +26 -0
- package/package.json +55 -0
- package/pi-extension/index.js +29 -0
- package/pi-extension/package.json +9 -0
- package/plugin.json +14 -0
- package/skills/necktie/SKILL.md +95 -0
- package/skills/necktie/agents/openai.yaml +6 -0
- package/skills/necktie/references/loop-protocol.md +85 -0
- package/skills/necktie/scripts/necktie_loop.py +199 -0
- package/skills/necktie-critique/SKILL.md +48 -0
- package/skills/necktie-critique/agents/openai.yaml +6 -0
- package/skills/necktie-reverse/SKILL.md +41 -0
- package/skills/necktie-reverse/agents/openai.yaml +6 -0
- package/skills/necktie-reverse/references/blueprint-template.md +54 -0
- package/skills/necktie-review/SKILL.md +61 -0
- package/skills/necktie-review/agents/openai.yaml +6 -0
- package/skills/necktie-review/references/reviewer-rubric.md +29 -0
- package/skills/necktie-review/scripts/validate_review.py +110 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const path = require("node:path");
|
|
6
|
+
|
|
7
|
+
function pluginRoot(env = process.env) {
|
|
8
|
+
return env.PLUGIN_ROOT || env.CLAUDE_PLUGIN_ROOT || path.resolve(__dirname, "..");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function coreContext(env = process.env) {
|
|
12
|
+
return fs.readFileSync(path.join(pluginRoot(env), "core", "necktie-core.md"), "utf8").trim();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function host(env = process.env) {
|
|
16
|
+
const compatibilityRoot = env.CLAUDE_PLUGIN_ROOT || "";
|
|
17
|
+
if (env.COPILOT_PLUGIN_DATA || compatibilityRoot.includes(".vscode/agent-plugins")) return "copilot";
|
|
18
|
+
if (env.QODER_SESSION_ID) return "qoder";
|
|
19
|
+
if (env.PLUGIN_ROOT || env.PLUGIN_DATA) return "codex";
|
|
20
|
+
return "claude";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function payload(event, env = process.env, explicitHost = "") {
|
|
24
|
+
const context = coreContext(env);
|
|
25
|
+
const detected = explicitHost || host(env);
|
|
26
|
+
if (detected === "copilot") return { additionalContext: context };
|
|
27
|
+
if (detected === "codex" || detected === "qoder" || detected === "gemini" || event === "SubagentStart") {
|
|
28
|
+
return { hookSpecificOutput: { hookEventName: event, additionalContext: context } };
|
|
29
|
+
}
|
|
30
|
+
return context;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function main(argv = process.argv.slice(2), env = process.env) {
|
|
34
|
+
const event = argv[0] || "SessionStart";
|
|
35
|
+
const hint = argv[1] || "";
|
|
36
|
+
const explicitHost = hint === "copilot" || hint === "qoder" ? hint : hint ? "gemini" : "";
|
|
37
|
+
const result = payload(event, env, explicitHost);
|
|
38
|
+
process.stdout.write(typeof result === "string" ? result : JSON.stringify(result));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (require.main === module) main();
|
|
42
|
+
|
|
43
|
+
module.exports = { coreContext, host, main, payload, pluginRoot };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Copy the hooks object into Qoder settings and replace NECKTIE_DIR with the absolute path to this checkout.",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"UserPromptSubmit": [
|
|
5
|
+
{
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "node \"NECKTIE_DIR/hooks/necktie-context.js\" UserPromptSubmit qoder"
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"PreToolUse": [
|
|
15
|
+
{
|
|
16
|
+
"matcher": "task|Task",
|
|
17
|
+
"hooks": [
|
|
18
|
+
{
|
|
19
|
+
"type": "command",
|
|
20
|
+
"command": "node \"NECKTIE_DIR/hooks/necktie-context.js\" SubagentStart qoder"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gillcash/necktie",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "The angel of late-stage capitalism for your AI agent: an always-on response check and an explicit bounded review loop.",
|
|
5
|
+
"keywords": ["agent-plugin", "agent-loop", "prompt-reversal", "review", "verification"],
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": {
|
|
8
|
+
"name": "gillcash",
|
|
9
|
+
"url": "https://github.com/gillcash"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/gillcash/necktie",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/gillcash/necktie.git"
|
|
15
|
+
},
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/gillcash/necktie/issues"
|
|
18
|
+
},
|
|
19
|
+
"main": "./.opencode/plugins/necktie.mjs",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": "./.opencode/plugins/necktie.mjs",
|
|
22
|
+
"./plugin": "./.opencode/plugins/necktie.mjs"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"AGENTS.md",
|
|
26
|
+
"plugin.json",
|
|
27
|
+
"core/",
|
|
28
|
+
"hooks/",
|
|
29
|
+
"skills/",
|
|
30
|
+
"commands/",
|
|
31
|
+
".opencode/",
|
|
32
|
+
".qoder/",
|
|
33
|
+
".qoder-plugin/",
|
|
34
|
+
"pi-extension/",
|
|
35
|
+
"assets/",
|
|
36
|
+
"LICENSE",
|
|
37
|
+
"NOTICE",
|
|
38
|
+
"!**/__pycache__/**",
|
|
39
|
+
"!**/*.pyc",
|
|
40
|
+
"!pi-extension/test/**"
|
|
41
|
+
],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build:adapters": "node scripts/build-adapters.js && node scripts/build-openclaw-skills.js",
|
|
44
|
+
"check:adapters": "node scripts/build-adapters.js --check",
|
|
45
|
+
"check:versions": "node scripts/check-versions.js",
|
|
46
|
+
"test": "npm run check:adapters && npm run check:versions && node --test tests/*.test.js tests/*.test.mjs && npm test --prefix pi-extension && npm test --prefix necktie-mcp && python -m unittest discover -s tests -p test_*.py"
|
|
47
|
+
},
|
|
48
|
+
"pi": {
|
|
49
|
+
"extensions": ["./pi-extension/index.js"],
|
|
50
|
+
"skills": ["./skills"]
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
|
|
7
|
+
export function coreContext() {
|
|
8
|
+
return fs.readFileSync(path.resolve(__dirname, "..", "core", "necktie-core.md"), "utf8").trim();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function sendSkill(pi, skill, args, ctx) {
|
|
12
|
+
const suffix = String(args || "").trim();
|
|
13
|
+
const message = suffix ? `/skill:${skill} ${suffix}` : `/skill:${skill}`;
|
|
14
|
+
if (ctx?.isIdle?.() === false) pi.sendUserMessage(message, { deliverAs: "followUp" });
|
|
15
|
+
else pi.sendUserMessage(message);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default function necktieExtension(pi) {
|
|
19
|
+
for (const skill of ["necktie", "necktie-critique", "necktie-reverse", "necktie-review"]) {
|
|
20
|
+
pi.registerCommand(skill, {
|
|
21
|
+
description: `Run /skill:${skill}`,
|
|
22
|
+
handler: (args, ctx) => sendSkill(pi, skill, args, ctx),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
pi.on("before_agent_start", async (event) => {
|
|
26
|
+
const base = event?.systemPrompt ? `${event.systemPrompt}\n\n` : "";
|
|
27
|
+
return { systemPrompt: `${base}${coreContext()}` };
|
|
28
|
+
});
|
|
29
|
+
}
|
package/plugin.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
|
+
"name": "necktie",
|
|
4
|
+
"version": "0.2.0",
|
|
5
|
+
"description": "The angel of late-stage capitalism for your AI agent: an always-on response check and an explicit bounded review loop.",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "gillcash",
|
|
8
|
+
"url": "https://github.com/gillcash"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/gillcash/necktie",
|
|
11
|
+
"repository": "https://github.com/gillcash/necktie",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"keywords": ["agent-loop", "prompt-reversal", "review", "verification"]
|
|
14
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: necktie
|
|
3
|
+
description: Run a goal from raw context to a reviewed, verified final deliverable through the explicit, bounded Necktie Loop. Use when the user invokes /necktie, $necktie, or @necktie; asks to reverse-engineer an iterative conversation into one reusable prompt; wants an independent critique and review gate; or explicitly requests the Necktie workflow.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Necktie
|
|
7
|
+
|
|
8
|
+
*the angel of late-stage capitalism for your AI agent*
|
|
9
|
+
|
|
10
|
+
Turn an underspecified or iterative request into one evidence-grounded deliverable and a reusable execution brief. Separate method context from domain evidence, critique the inquiry before optimizing the answer, and stop only at a verified result, a material blocker, or the circuit breaker.
|
|
11
|
+
|
|
12
|
+
## Start the run
|
|
13
|
+
|
|
14
|
+
1. Treat the text following `/necktie` or `$necktie` as the goal and any attached files, links, or named paths as candidate sources.
|
|
15
|
+
2. Read [references/loop-protocol.md](references/loop-protocol.md) completely before running the loop.
|
|
16
|
+
3. Maintain a compact run packet in memory. When the user requests an audit trail, resumability, or files, initialize `.necktie/run.json` with `scripts/necktie_loop.py` and update it at phase boundaries.
|
|
17
|
+
4. Ask a question only when the answer would materially change the objective, evidence, authority, or deliverable. Otherwise record the assumption and proceed.
|
|
18
|
+
|
|
19
|
+
## Run the loop
|
|
20
|
+
|
|
21
|
+
### 1. Frame
|
|
22
|
+
|
|
23
|
+
State the outcome, intended audience, acceptance criteria, constraints, and non-goals. Build a source ledger that labels every input as one of:
|
|
24
|
+
|
|
25
|
+
- `evidence`: may support claims in the final output.
|
|
26
|
+
- `method`: controls how to work but does not prove domain claims.
|
|
27
|
+
- `constraint`: governs scope, format, safety, or authority.
|
|
28
|
+
- `prior-output`: useful as a hypothesis or preference, not independent evidence.
|
|
29
|
+
|
|
30
|
+
### 2. Establish a baseline
|
|
31
|
+
|
|
32
|
+
Inspect the relevant sources and sketch the smallest plausible answer or approach. Record its assumptions and known weaknesses. Do not polish it into the final artifact yet.
|
|
33
|
+
|
|
34
|
+
### 3. Critique
|
|
35
|
+
|
|
36
|
+
Apply the `$necktie-critique` skill to the goal, baseline, source ledger, and acceptance criteria. Require it to challenge the framing, identify what the user may have overlooked relative to the goal, name the strongest unasked expert question, and distinguish material questions from optional improvements.
|
|
37
|
+
|
|
38
|
+
### 4. Reverse
|
|
39
|
+
|
|
40
|
+
Resolve critique findings with evidence or explicit assumptions. Then apply `$necktie-reverse` to compile the complete successful trajectory into one executable brief. The brief must be usable in a fresh session without relying on hidden conversation state.
|
|
41
|
+
|
|
42
|
+
### 5. Execute
|
|
43
|
+
|
|
44
|
+
Produce the requested artifact from the executable brief and the raw evidence sources. Do not treat the baseline, critique, or previous answer as proof. Follow any applicable artifact skill and perform proportional checks while building.
|
|
45
|
+
|
|
46
|
+
### 6. Review
|
|
47
|
+
|
|
48
|
+
Apply `$necktie-review` with the exact executable brief, candidate artifact, acceptance criteria, source ledger, and compact evidence packet. Use a separate reviewer agent when the host supports it and policy permits; otherwise perform a fresh-context review that does not edit the artifact while judging it.
|
|
49
|
+
|
|
50
|
+
Handle the decision exactly:
|
|
51
|
+
|
|
52
|
+
- `APPROVE`: proceed to verification.
|
|
53
|
+
- `REVISE`: fix the cited material findings, then return the changed artifact to review.
|
|
54
|
+
- `BLOCK`: stop and report the missing evidence, authority, or user decision.
|
|
55
|
+
|
|
56
|
+
Never route around a reviewer denial by restating the same action. Stop after three revision decisions or after three consecutive reviews with the same unresolved issue.
|
|
57
|
+
|
|
58
|
+
### 7. Verify
|
|
59
|
+
|
|
60
|
+
Test, render, calculate, inspect, or otherwise exercise the artifact in the environment where it will be used. If verification exposes a material defect and budget remains, revise and review again. Otherwise block with the concrete failure.
|
|
61
|
+
|
|
62
|
+
Deliver:
|
|
63
|
+
|
|
64
|
+
1. The final artifact or a precise link/path to it.
|
|
65
|
+
2. The reusable executable brief.
|
|
66
|
+
3. A concise verification record and any limitations.
|
|
67
|
+
4. What the user may not have considered, if material.
|
|
68
|
+
5. The strongest unasked question, with its practical consequence.
|
|
69
|
+
|
|
70
|
+
Do not reveal private chain-of-thought. Provide concise decisions, evidence, assumptions, and test results instead.
|
|
71
|
+
|
|
72
|
+
## Use the minimum sufficient intervention
|
|
73
|
+
|
|
74
|
+
At every phase, prefer the earliest option that meets the acceptance criteria:
|
|
75
|
+
|
|
76
|
+
1. Do not create anything if explanation or a decision is enough.
|
|
77
|
+
2. Reuse a trusted existing asset.
|
|
78
|
+
3. Use the host's built-in capability or an installed skill.
|
|
79
|
+
4. Use standard-library automation.
|
|
80
|
+
5. Add the smallest new implementation.
|
|
81
|
+
|
|
82
|
+
Do not expand scope merely because the loop discovers an attractive adjacent task.
|
|
83
|
+
|
|
84
|
+
## Audit the state machine
|
|
85
|
+
|
|
86
|
+
Use the bundled controller only when persistence is useful:
|
|
87
|
+
|
|
88
|
+
```text
|
|
89
|
+
python skills/necktie/scripts/necktie_loop.py init --goal "..." --output .necktie/run.json
|
|
90
|
+
python skills/necktie/scripts/necktie_loop.py transition --file .necktie/run.json --to baseline --note "Sources classified"
|
|
91
|
+
python skills/necktie/scripts/necktie_loop.py review --file .necktie/run.json --decision REVISE --reason "Unsupported claim" --issue-signature evidence-gap
|
|
92
|
+
python skills/necktie/scripts/necktie_loop.py show --file .necktie/run.json
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The controller records decisions, not hidden reasoning. It enforces valid transitions and the review circuit breaker; it does not generate or judge the artifact.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Necktie loop protocol
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Use this protocol to transform an initial request and its source context into a reusable brief and a verified final artifact. The loop combines prompt reversal, inquiry critique, blueprint-first execution, independent review, and a finite circuit breaker.
|
|
6
|
+
|
|
7
|
+
## State model
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
frame -> baseline -> critique -> reverse -> execute -> review
|
|
11
|
+
^ |
|
|
12
|
+
| v
|
|
13
|
+
revise <- REVISE
|
|
14
|
+
|
|
|
15
|
+
APPROVE -> verify -> complete
|
|
16
|
+
|
|
|
17
|
+
BLOCK ----------> blocked
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Verification failure returns to `revise` only while review budget remains. A blocked run resumes only when new evidence, authority, or a material user decision changes the conditions.
|
|
21
|
+
|
|
22
|
+
## Run packet
|
|
23
|
+
|
|
24
|
+
Maintain only information needed to reproduce and audit decisions:
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"goal": "Desired real-world outcome",
|
|
29
|
+
"state": "frame",
|
|
30
|
+
"audience": "Who will use the result",
|
|
31
|
+
"deliverables": [],
|
|
32
|
+
"acceptance_criteria": [],
|
|
33
|
+
"constraints": [],
|
|
34
|
+
"non_goals": [],
|
|
35
|
+
"sources": [
|
|
36
|
+
{"id": "S1", "kind": "evidence", "location": "...", "use": "..."}
|
|
37
|
+
],
|
|
38
|
+
"assumptions": [],
|
|
39
|
+
"strongest_unasked_question": "",
|
|
40
|
+
"review_history": [],
|
|
41
|
+
"verification": []
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Do not store secrets or private reasoning. Record concise rationales, citations, hashes, commands, or test results when useful.
|
|
46
|
+
|
|
47
|
+
## Role separation
|
|
48
|
+
|
|
49
|
+
The author creates and revises. The critic challenges the inquiry before execution. The reviewer judges the exact candidate after execution. Prefer separate agent contexts when the host provides them, but never broaden permissions or expose more evidence than the role requires.
|
|
50
|
+
|
|
51
|
+
If only one agent is available, emulate separation by finishing and freezing the author packet, then starting a read-only review pass from the exact brief and evidence index. Do not edit until the decision has been recorded.
|
|
52
|
+
|
|
53
|
+
## Source discipline
|
|
54
|
+
|
|
55
|
+
- `evidence` supports domain claims.
|
|
56
|
+
- `method` describes how to reason or work.
|
|
57
|
+
- `constraint` controls behavior, authority, scope, or form.
|
|
58
|
+
- `prior-output` preserves preferences or hypotheses but cannot corroborate itself.
|
|
59
|
+
|
|
60
|
+
When executing the reversed brief, return to raw evidence rather than paraphrasing the baseline. This prevents iterative wording from turning into false support.
|
|
61
|
+
|
|
62
|
+
## Bounded improvement
|
|
63
|
+
|
|
64
|
+
The loop permits at most three `REVISE` decisions.
|
|
65
|
+
|
|
66
|
+
Open the circuit and block when any condition occurs:
|
|
67
|
+
|
|
68
|
+
- the same issue signature survives three consecutive reviews;
|
|
69
|
+
- three revision decisions have been recorded;
|
|
70
|
+
- the next step needs new authority, unavailable evidence, or a material user choice;
|
|
71
|
+
- further work would expand beyond the agreed goal.
|
|
72
|
+
|
|
73
|
+
Do not evade a denial by renaming the same action or changing only its presentation. A new pass must make a material change tied to a finding.
|
|
74
|
+
|
|
75
|
+
## Completion contract
|
|
76
|
+
|
|
77
|
+
A run is complete only when:
|
|
78
|
+
|
|
79
|
+
1. the artifact and executable brief exist;
|
|
80
|
+
2. all critical and major findings are resolved;
|
|
81
|
+
3. relevant verification passes in the target environment;
|
|
82
|
+
4. remaining limitations are explicit;
|
|
83
|
+
5. the handoff identifies the strongest unasked question when it could change future action.
|
|
84
|
+
|
|
85
|
+
Necktie has no persistent operating mode. The run packet records one explicitly invoked loop; it does not turn Necktie Core on or off.
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Create and advance an auditable, deterministic Necktie Loop packet."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
import json
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
import sys
|
|
11
|
+
import uuid
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
SCHEMA_VERSION = "2.0"
|
|
15
|
+
REVISION_LIMIT = 3
|
|
16
|
+
STATES = {
|
|
17
|
+
"frame", "baseline", "critique", "reverse", "execute", "review",
|
|
18
|
+
"revise", "verify", "complete", "blocked",
|
|
19
|
+
}
|
|
20
|
+
ALLOWED_TRANSITIONS = {
|
|
21
|
+
"frame": {"baseline"},
|
|
22
|
+
"baseline": {"critique"},
|
|
23
|
+
"critique": {"reverse"},
|
|
24
|
+
"reverse": {"execute"},
|
|
25
|
+
"execute": {"review"},
|
|
26
|
+
"revise": {"review"},
|
|
27
|
+
"verify": {"complete", "revise"},
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class LoopError(ValueError):
|
|
32
|
+
"""Raised for an invalid run packet or state transition."""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def utc_now() -> str:
|
|
36
|
+
return datetime.now(timezone.utc).replace(microsecond=0).isoformat()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def event(kind: str, **details: object) -> dict[str, object]:
|
|
40
|
+
return {"at": utc_now(), "kind": kind, **details}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def new_packet(goal: str) -> dict[str, object]:
|
|
44
|
+
goal = goal.strip()
|
|
45
|
+
if not goal:
|
|
46
|
+
raise LoopError("goal must not be empty")
|
|
47
|
+
return {
|
|
48
|
+
"schema_version": SCHEMA_VERSION,
|
|
49
|
+
"run_id": str(uuid.uuid4()),
|
|
50
|
+
"created_at": utc_now(),
|
|
51
|
+
"updated_at": utc_now(),
|
|
52
|
+
"goal": goal,
|
|
53
|
+
"state": "frame",
|
|
54
|
+
"audience": "",
|
|
55
|
+
"deliverables": [],
|
|
56
|
+
"acceptance_criteria": [],
|
|
57
|
+
"constraints": [],
|
|
58
|
+
"non_goals": [],
|
|
59
|
+
"sources": [],
|
|
60
|
+
"assumptions": [],
|
|
61
|
+
"strongest_unasked_question": "",
|
|
62
|
+
"review_history": [],
|
|
63
|
+
"verification": [],
|
|
64
|
+
"circuit_breaker": None,
|
|
65
|
+
"history": [event("initialized", state="frame")],
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def validate_packet(packet: object) -> dict[str, object]:
|
|
70
|
+
if not isinstance(packet, dict):
|
|
71
|
+
raise LoopError("run packet must be a JSON object")
|
|
72
|
+
required = {"schema_version", "run_id", "goal", "state", "review_history", "history"}
|
|
73
|
+
missing = sorted(required - packet.keys())
|
|
74
|
+
if missing:
|
|
75
|
+
raise LoopError(f"run packet is missing: {', '.join(missing)}")
|
|
76
|
+
if packet["schema_version"] != SCHEMA_VERSION:
|
|
77
|
+
raise LoopError(f"unsupported schema_version: {packet['schema_version']}")
|
|
78
|
+
if packet["state"] not in STATES:
|
|
79
|
+
raise LoopError(f"unsupported state: {packet['state']}")
|
|
80
|
+
if not isinstance(packet["review_history"], list) or not isinstance(packet["history"], list):
|
|
81
|
+
raise LoopError("review_history and history must be arrays")
|
|
82
|
+
return packet
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def load_packet(path: Path) -> dict[str, object]:
|
|
86
|
+
try:
|
|
87
|
+
return validate_packet(json.loads(path.read_text(encoding="utf-8")))
|
|
88
|
+
except FileNotFoundError as exc:
|
|
89
|
+
raise LoopError(f"run packet not found: {path}") from exc
|
|
90
|
+
except json.JSONDecodeError as exc:
|
|
91
|
+
raise LoopError(f"invalid JSON in {path}: {exc}") from exc
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def save_packet(path: Path, packet: dict[str, object]) -> None:
|
|
95
|
+
validate_packet(packet)
|
|
96
|
+
packet["updated_at"] = utc_now()
|
|
97
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
98
|
+
temporary = path.with_suffix(path.suffix + ".tmp")
|
|
99
|
+
temporary.write_text(json.dumps(packet, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
100
|
+
temporary.replace(path)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def transition(packet: dict[str, object], target: str, note: str) -> None:
|
|
104
|
+
current = str(packet["state"])
|
|
105
|
+
if target not in ALLOWED_TRANSITIONS.get(current, set()):
|
|
106
|
+
allowed = ", ".join(sorted(ALLOWED_TRANSITIONS.get(current, set()))) or "none"
|
|
107
|
+
raise LoopError(f"cannot transition from {current} to {target}; allowed: {allowed}")
|
|
108
|
+
packet["state"] = target
|
|
109
|
+
packet["history"].append(event("transition", previous=current, state=target, note=note.strip()))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def record_review(packet: dict[str, object], decision: str, reason: str, issue_signature: str) -> None:
|
|
113
|
+
if packet["state"] != "review":
|
|
114
|
+
raise LoopError(f"review decisions require state=review, found {packet['state']}")
|
|
115
|
+
decision = decision.upper()
|
|
116
|
+
if decision not in {"APPROVE", "REVISE", "BLOCK"}:
|
|
117
|
+
raise LoopError(f"unsupported review decision: {decision}")
|
|
118
|
+
reason = reason.strip()
|
|
119
|
+
if not reason:
|
|
120
|
+
raise LoopError("review reason must not be empty")
|
|
121
|
+
signature = issue_signature.strip()
|
|
122
|
+
if decision == "REVISE" and not signature:
|
|
123
|
+
raise LoopError("REVISE requires --issue-signature")
|
|
124
|
+
|
|
125
|
+
reviews = packet["review_history"]
|
|
126
|
+
reviews.append(event("review", attempt=len(reviews) + 1, decision=decision,
|
|
127
|
+
reason=reason, issue_signature=signature))
|
|
128
|
+
if decision == "APPROVE":
|
|
129
|
+
packet["state"] = "verify"
|
|
130
|
+
elif decision == "BLOCK":
|
|
131
|
+
packet["state"] = "blocked"
|
|
132
|
+
packet["circuit_breaker"] = "reviewer-blocked"
|
|
133
|
+
else:
|
|
134
|
+
revision_count = sum(item["decision"] == "REVISE" for item in reviews)
|
|
135
|
+
same_issue_count = 0
|
|
136
|
+
for item in reversed(reviews):
|
|
137
|
+
if item["decision"] == "REVISE" and item["issue_signature"] == signature:
|
|
138
|
+
same_issue_count += 1
|
|
139
|
+
else:
|
|
140
|
+
break
|
|
141
|
+
if same_issue_count >= 3:
|
|
142
|
+
packet["state"] = "blocked"
|
|
143
|
+
packet["circuit_breaker"] = "same-issue-three-times"
|
|
144
|
+
elif revision_count >= REVISION_LIMIT:
|
|
145
|
+
packet["state"] = "blocked"
|
|
146
|
+
packet["circuit_breaker"] = "revision-limit-reached"
|
|
147
|
+
else:
|
|
148
|
+
packet["state"] = "revise"
|
|
149
|
+
packet["history"].append(event("review-decision", decision=decision,
|
|
150
|
+
state=packet["state"], reason=reason))
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
154
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
155
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
156
|
+
initialize = subparsers.add_parser("init", help="create a new run packet")
|
|
157
|
+
initialize.add_argument("--goal", required=True)
|
|
158
|
+
initialize.add_argument("--output", type=Path, required=True)
|
|
159
|
+
advance = subparsers.add_parser("transition", help="advance to an allowed state")
|
|
160
|
+
advance.add_argument("--file", type=Path, required=True)
|
|
161
|
+
advance.add_argument("--to", choices=sorted(STATES), required=True)
|
|
162
|
+
advance.add_argument("--note", default="")
|
|
163
|
+
review = subparsers.add_parser("review", help="record an independent review decision")
|
|
164
|
+
review.add_argument("--file", type=Path, required=True)
|
|
165
|
+
review.add_argument("--decision", choices=["APPROVE", "REVISE", "BLOCK"], required=True)
|
|
166
|
+
review.add_argument("--reason", required=True)
|
|
167
|
+
review.add_argument("--issue-signature", default="")
|
|
168
|
+
show = subparsers.add_parser("show", help="validate and print a run packet")
|
|
169
|
+
show.add_argument("--file", type=Path, required=True)
|
|
170
|
+
return parser
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def main(argv: list[str] | None = None) -> int:
|
|
174
|
+
args = build_parser().parse_args(argv)
|
|
175
|
+
try:
|
|
176
|
+
if args.command == "init":
|
|
177
|
+
packet = new_packet(args.goal)
|
|
178
|
+
save_packet(args.output, packet)
|
|
179
|
+
print(f"initialized {packet['run_id']} at {args.output}")
|
|
180
|
+
elif args.command == "transition":
|
|
181
|
+
packet = load_packet(args.file)
|
|
182
|
+
transition(packet, args.to, args.note)
|
|
183
|
+
save_packet(args.file, packet)
|
|
184
|
+
print(f"state={packet['state']}")
|
|
185
|
+
elif args.command == "review":
|
|
186
|
+
packet = load_packet(args.file)
|
|
187
|
+
record_review(packet, args.decision, args.reason, args.issue_signature)
|
|
188
|
+
save_packet(args.file, packet)
|
|
189
|
+
print(f"state={packet['state']}")
|
|
190
|
+
else:
|
|
191
|
+
print(json.dumps(load_packet(args.file), indent=2, ensure_ascii=False))
|
|
192
|
+
except LoopError as exc:
|
|
193
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
194
|
+
return 2
|
|
195
|
+
return 0
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
if __name__ == "__main__":
|
|
199
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: necktie-critique
|
|
3
|
+
description: Critique and expand an inquiry before execution by testing goal alignment, assumptions, omitted perspectives, and evidence needs. Use inside the Necktie loop or when the user asks what they overlooked, wants the strongest unasked expert question, requests a red-team of the framing, or needs material follow-up questions rather than a direct answer.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Necktie Critique
|
|
7
|
+
|
|
8
|
+
Critique the inquiry, not merely the current answer. Broaden the user's view without turning the task into an endless discovery interview.
|
|
9
|
+
|
|
10
|
+
## Inputs
|
|
11
|
+
|
|
12
|
+
Obtain the goal, intended audience, acceptance criteria, source ledger, constraints, and any baseline approach. If one is missing, infer it when safe and label the assumption.
|
|
13
|
+
|
|
14
|
+
## Method
|
|
15
|
+
|
|
16
|
+
1. Restate the actual decision or outcome in one sentence.
|
|
17
|
+
2. Test whether the requested deliverable is a means to that outcome or has become the goal by accident.
|
|
18
|
+
3. Check the baseline for hidden assumptions, missing stakeholders, incentives, failure modes, alternative explanations, data limitations, and implementation constraints.
|
|
19
|
+
4. Identify contradictions between the goal, evidence, requested format, and available authority.
|
|
20
|
+
5. Simulate the most relevant genuine subject-matter expert. Ask what that expert would need to know before trusting or acting on the result.
|
|
21
|
+
6. Select the single strongest unasked question: the question whose answer would most change the plan, conclusion, or risk.
|
|
22
|
+
7. Separate questions into:
|
|
23
|
+
- `material-now`: execution should pause because different answers produce meaningfully different outputs.
|
|
24
|
+
- `assumption-safe`: proceed under a clearly stated default.
|
|
25
|
+
- `optional-later`: useful but outside the current scope.
|
|
26
|
+
8. Reframe the inquiry so it targets the user's outcome, includes the necessary controls, and remains answerable from the permitted sources.
|
|
27
|
+
|
|
28
|
+
Do not invent domain facts, confuse method guidance with evidence, or criticize stylistic preferences that do not affect the goal.
|
|
29
|
+
|
|
30
|
+
## Output
|
|
31
|
+
|
|
32
|
+
Return this compact structure:
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
Outcome test: ...
|
|
36
|
+
What is missing or misframed:
|
|
37
|
+
- ...
|
|
38
|
+
Assumptions to expose:
|
|
39
|
+
- ...
|
|
40
|
+
Strongest unasked question: ...
|
|
41
|
+
Why it matters: ...
|
|
42
|
+
Material questions for the user:
|
|
43
|
+
- ... (or "None; proceed with the stated assumptions.")
|
|
44
|
+
Reframed inquiry: ...
|
|
45
|
+
Readiness: READY | NEEDS-ANSWER | BLOCKED
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Use `NEEDS-ANSWER` only for a material user choice. Use `BLOCKED` only when required evidence or authority cannot be obtained within scope.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: necktie-reverse
|
|
3
|
+
description: Reverse-engineer an iterative conversation, critique, and successful refinements into one fresh-session executable brief. Use inside the Necktie loop, when the user asks for prompt reversal, when a sequence of revisions should become a reusable prompt, or when execution must be separated from prior answer leakage.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Necktie Reverse
|
|
7
|
+
|
|
8
|
+
Compile the useful decisions from an iterative exchange into one self-contained brief that could have produced the desired result in a fresh session.
|
|
9
|
+
|
|
10
|
+
## Build the brief
|
|
11
|
+
|
|
12
|
+
1. Read [references/blueprint-template.md](references/blueprint-template.md) completely.
|
|
13
|
+
2. Extract the stable goal, audience, constraints, approved refinements, source hierarchy, required deliverables, acceptance criteria, and verification requirements.
|
|
14
|
+
3. Preserve explicit user choices. Resolve contradictions by favoring the latest explicit instruction, then higher-authority constraints, then the option best aligned with the stated goal.
|
|
15
|
+
4. Incorporate critique findings that materially improve correctness, usefulness, risk control, or verifiability.
|
|
16
|
+
5. Exclude conversational debris, abandoned approaches, praise, hidden reasoning, and claims that appeared only in prior outputs.
|
|
17
|
+
6. Require fresh execution from raw sources. Label prior outputs as hypotheses or style references unless independently supported.
|
|
18
|
+
7. Include a reviewer contract with an exact decision vocabulary and stopping rule.
|
|
19
|
+
8. Make the brief specific enough to execute yet independent of a particular model, tool name, or unavailable session state.
|
|
20
|
+
|
|
21
|
+
## Quality test
|
|
22
|
+
|
|
23
|
+
Before returning the brief, verify that a capable agent in a new session could answer all of these from the brief alone:
|
|
24
|
+
|
|
25
|
+
- What outcome matters, for whom, and why?
|
|
26
|
+
- Which sources can prove claims, and which only guide the method?
|
|
27
|
+
- What is in and out of scope?
|
|
28
|
+
- What must be delivered and in what form?
|
|
29
|
+
- What constitutes acceptance or failure?
|
|
30
|
+
- What should be checked, by whom, and when should the loop stop?
|
|
31
|
+
|
|
32
|
+
If a material answer is missing, ask one focused question or state a safe assumption. Do not smuggle unresolved ambiguity into vague language.
|
|
33
|
+
|
|
34
|
+
## Output
|
|
35
|
+
|
|
36
|
+
Return:
|
|
37
|
+
|
|
38
|
+
1. `Executable brief` in a single copyable block using the template headings.
|
|
39
|
+
2. `Compilation notes` listing only material assumptions, excluded prior-output claims, and unresolved limitations.
|
|
40
|
+
|
|
41
|
+
Do not include a transcript summary or chain-of-thought.
|