intent-cli-python 0.6.0__tar.gz → 1.0.0__tar.gz
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.
- {intent_cli_python-0.6.0 → intent_cli_python-1.0.0}/PKG-INFO +28 -2
- {intent_cli_python-0.6.0 → intent_cli_python-1.0.0}/README.md +26 -0
- {intent_cli_python-0.6.0 → intent_cli_python-1.0.0}/pyproject.toml +2 -2
- {intent_cli_python-0.6.0 → intent_cli_python-1.0.0}/src/intent_cli/cli.py +6 -2
- {intent_cli_python-0.6.0 → intent_cli_python-1.0.0}/src/intent_cli/store.py +1 -1
- {intent_cli_python-0.6.0 → intent_cli_python-1.0.0}/src/intent_cli_python.egg-info/PKG-INFO +28 -2
- {intent_cli_python-0.6.0 → intent_cli_python-1.0.0}/src/intent_cli_python.egg-info/SOURCES.txt +2 -1
- intent_cli_python-1.0.0/tests/test_cli.py +304 -0
- {intent_cli_python-0.6.0 → intent_cli_python-1.0.0}/LICENSE +0 -0
- {intent_cli_python-0.6.0 → intent_cli_python-1.0.0}/setup.cfg +0 -0
- {intent_cli_python-0.6.0 → intent_cli_python-1.0.0}/src/intent_cli/__init__.py +0 -0
- {intent_cli_python-0.6.0 → intent_cli_python-1.0.0}/src/intent_cli/__main__.py +0 -0
- {intent_cli_python-0.6.0 → intent_cli_python-1.0.0}/src/intent_cli/output.py +0 -0
- {intent_cli_python-0.6.0 → intent_cli_python-1.0.0}/src/intent_cli_python.egg-info/dependency_links.txt +0 -0
- {intent_cli_python-0.6.0 → intent_cli_python-1.0.0}/src/intent_cli_python.egg-info/entry_points.txt +0 -0
- {intent_cli_python-0.6.0 → intent_cli_python-1.0.0}/src/intent_cli_python.egg-info/top_level.txt +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: intent-cli-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: Semantic history for agent-driven development. Records what you did and why.
|
|
5
5
|
Author: Zeng Deyang
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/dozybot001/Intent
|
|
8
8
|
Project-URL: Repository, https://github.com/dozybot001/Intent
|
|
9
9
|
Keywords: agent,git,semantic-history,intent,developer-tools
|
|
10
|
-
Classifier: Development Status ::
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
11
|
Classifier: Environment :: Console
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -40,14 +40,34 @@ Intent CLI gives AI agents a structured way to track goals, interactions, and de
|
|
|
40
40
|
|
|
41
41
|
Objects link automatically: creating an intent attaches all active decisions; creating a decision attaches all active intents. Relationships are always bidirectional and append-only.
|
|
42
42
|
|
|
43
|
+
### How decisions are created
|
|
44
|
+
|
|
45
|
+
Decisions require human involvement. Two paths:
|
|
46
|
+
|
|
47
|
+
- **Explicit**: include `decision-[text]` (or `决定-[text]`) in your query and the agent creates it directly. E.g. "decision-all API responses use envelope format"
|
|
48
|
+
- **Agent-proposed**: the agent spots a potential long-term constraint in conversation and asks you to confirm before recording it
|
|
49
|
+
|
|
43
50
|
## Install
|
|
44
51
|
|
|
45
52
|
```bash
|
|
53
|
+
# Clone the repository
|
|
54
|
+
git clone https://github.com/dozybot001/Intent.git
|
|
55
|
+
|
|
56
|
+
# Install the CLI (pipx recommended)
|
|
57
|
+
pipx install intent-cli-python
|
|
58
|
+
|
|
59
|
+
# Or using pip
|
|
46
60
|
pip install intent-cli-python
|
|
47
61
|
```
|
|
48
62
|
|
|
49
63
|
Requires Python 3.9+ and Git.
|
|
50
64
|
|
|
65
|
+
### Add the Claude Code skill
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npx skills add dozybot001/Intent
|
|
69
|
+
```
|
|
70
|
+
|
|
51
71
|
## Quick start
|
|
52
72
|
|
|
53
73
|
```bash
|
|
@@ -136,6 +156,12 @@ All data lives in `.intent/` at your git repo root:
|
|
|
136
156
|
decision-001.json
|
|
137
157
|
```
|
|
138
158
|
|
|
159
|
+
## Docs
|
|
160
|
+
|
|
161
|
+
- [Vision](docs/EN/vision.md) — why semantic history matters. **If this project interests you, start here.**
|
|
162
|
+
- [CLI Design](docs/EN/cli.md) — object model, commands, JSON contract
|
|
163
|
+
- [Roadmap](docs/EN/roadmap.md) — phase plan
|
|
164
|
+
|
|
139
165
|
## License
|
|
140
166
|
|
|
141
167
|
MIT
|
|
@@ -16,14 +16,34 @@ Intent CLI gives AI agents a structured way to track goals, interactions, and de
|
|
|
16
16
|
|
|
17
17
|
Objects link automatically: creating an intent attaches all active decisions; creating a decision attaches all active intents. Relationships are always bidirectional and append-only.
|
|
18
18
|
|
|
19
|
+
### How decisions are created
|
|
20
|
+
|
|
21
|
+
Decisions require human involvement. Two paths:
|
|
22
|
+
|
|
23
|
+
- **Explicit**: include `decision-[text]` (or `决定-[text]`) in your query and the agent creates it directly. E.g. "decision-all API responses use envelope format"
|
|
24
|
+
- **Agent-proposed**: the agent spots a potential long-term constraint in conversation and asks you to confirm before recording it
|
|
25
|
+
|
|
19
26
|
## Install
|
|
20
27
|
|
|
21
28
|
```bash
|
|
29
|
+
# Clone the repository
|
|
30
|
+
git clone https://github.com/dozybot001/Intent.git
|
|
31
|
+
|
|
32
|
+
# Install the CLI (pipx recommended)
|
|
33
|
+
pipx install intent-cli-python
|
|
34
|
+
|
|
35
|
+
# Or using pip
|
|
22
36
|
pip install intent-cli-python
|
|
23
37
|
```
|
|
24
38
|
|
|
25
39
|
Requires Python 3.9+ and Git.
|
|
26
40
|
|
|
41
|
+
### Add the Claude Code skill
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx skills add dozybot001/Intent
|
|
45
|
+
```
|
|
46
|
+
|
|
27
47
|
## Quick start
|
|
28
48
|
|
|
29
49
|
```bash
|
|
@@ -112,6 +132,12 @@ All data lives in `.intent/` at your git repo root:
|
|
|
112
132
|
decision-001.json
|
|
113
133
|
```
|
|
114
134
|
|
|
135
|
+
## Docs
|
|
136
|
+
|
|
137
|
+
- [Vision](docs/EN/vision.md) — why semantic history matters. **If this project interests you, start here.**
|
|
138
|
+
- [CLI Design](docs/EN/cli.md) — object model, commands, JSON contract
|
|
139
|
+
- [Roadmap](docs/EN/roadmap.md) — phase plan
|
|
140
|
+
|
|
115
141
|
## License
|
|
116
142
|
|
|
117
143
|
MIT
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "intent-cli-python"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "1.0.0"
|
|
8
8
|
description = "Semantic history for agent-driven development. Records what you did and why."
|
|
9
9
|
requires-python = ">=3.9"
|
|
10
10
|
readme = "README.md"
|
|
@@ -14,7 +14,7 @@ authors = [
|
|
|
14
14
|
]
|
|
15
15
|
keywords = ["agent", "git", "semantic-history", "intent", "developer-tools"]
|
|
16
16
|
classifiers = [
|
|
17
|
-
"Development Status ::
|
|
17
|
+
"Development Status :: 5 - Production/Stable",
|
|
18
18
|
"Environment :: Console",
|
|
19
19
|
"Intended Audience :: Developers",
|
|
20
20
|
"Programming Language :: Python :: 3",
|
|
@@ -11,7 +11,7 @@ from intent_cli.store import (
|
|
|
11
11
|
next_id, read_object, write_object, list_objects, read_config,
|
|
12
12
|
)
|
|
13
13
|
|
|
14
|
-
VERSION = "0.
|
|
14
|
+
VERSION = "1.0.0"
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def _now():
|
|
@@ -98,7 +98,7 @@ def cmd_inspect(_args):
|
|
|
98
98
|
|
|
99
99
|
print(json.dumps({
|
|
100
100
|
"ok": True,
|
|
101
|
-
"schema_version": config.get("schema_version", "0
|
|
101
|
+
"schema_version": config.get("schema_version", "1.0"),
|
|
102
102
|
"active_intents": active_intents,
|
|
103
103
|
"suspend_intents": suspend_intents,
|
|
104
104
|
"active_decisions": active_decisions,
|
|
@@ -129,6 +129,7 @@ def cmd_intent_create(args):
|
|
|
129
129
|
"title": args.title,
|
|
130
130
|
"status": "active",
|
|
131
131
|
"source_query": args.query,
|
|
132
|
+
"rationale": args.rationale,
|
|
132
133
|
"decision_ids": decision_ids,
|
|
133
134
|
"snap_ids": [],
|
|
134
135
|
}
|
|
@@ -234,6 +235,7 @@ def cmd_snap_create(args):
|
|
|
234
235
|
"status": "active",
|
|
235
236
|
"intent_id": intent_id,
|
|
236
237
|
"query": args.query,
|
|
238
|
+
"rationale": args.rationale,
|
|
237
239
|
"summary": args.summary,
|
|
238
240
|
"feedback": args.feedback,
|
|
239
241
|
}
|
|
@@ -390,6 +392,7 @@ def main():
|
|
|
390
392
|
p = s_intent.add_parser("create")
|
|
391
393
|
p.add_argument("title")
|
|
392
394
|
p.add_argument("--query", default="")
|
|
395
|
+
p.add_argument("--rationale", default="")
|
|
393
396
|
|
|
394
397
|
p = s_intent.add_parser("list")
|
|
395
398
|
p.add_argument("--status", default=None)
|
|
@@ -414,6 +417,7 @@ def main():
|
|
|
414
417
|
p.add_argument("title")
|
|
415
418
|
p.add_argument("--intent", required=True)
|
|
416
419
|
p.add_argument("--query", default="")
|
|
420
|
+
p.add_argument("--rationale", default="")
|
|
417
421
|
p.add_argument("--summary", default="")
|
|
418
422
|
p.add_argument("--feedback", default="")
|
|
419
423
|
|
|
@@ -43,7 +43,7 @@ def init_workspace():
|
|
|
43
43
|
d.mkdir()
|
|
44
44
|
for sub in SUBDIRS.values():
|
|
45
45
|
(d / sub).mkdir()
|
|
46
|
-
(d / "config.json").write_text(json.dumps({"schema_version": "0
|
|
46
|
+
(d / "config.json").write_text(json.dumps({"schema_version": "1.0"}, indent=2))
|
|
47
47
|
return d, None
|
|
48
48
|
|
|
49
49
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: intent-cli-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: Semantic history for agent-driven development. Records what you did and why.
|
|
5
5
|
Author: Zeng Deyang
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/dozybot001/Intent
|
|
8
8
|
Project-URL: Repository, https://github.com/dozybot001/Intent
|
|
9
9
|
Keywords: agent,git,semantic-history,intent,developer-tools
|
|
10
|
-
Classifier: Development Status ::
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
11
|
Classifier: Environment :: Console
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -40,14 +40,34 @@ Intent CLI gives AI agents a structured way to track goals, interactions, and de
|
|
|
40
40
|
|
|
41
41
|
Objects link automatically: creating an intent attaches all active decisions; creating a decision attaches all active intents. Relationships are always bidirectional and append-only.
|
|
42
42
|
|
|
43
|
+
### How decisions are created
|
|
44
|
+
|
|
45
|
+
Decisions require human involvement. Two paths:
|
|
46
|
+
|
|
47
|
+
- **Explicit**: include `decision-[text]` (or `决定-[text]`) in your query and the agent creates it directly. E.g. "decision-all API responses use envelope format"
|
|
48
|
+
- **Agent-proposed**: the agent spots a potential long-term constraint in conversation and asks you to confirm before recording it
|
|
49
|
+
|
|
43
50
|
## Install
|
|
44
51
|
|
|
45
52
|
```bash
|
|
53
|
+
# Clone the repository
|
|
54
|
+
git clone https://github.com/dozybot001/Intent.git
|
|
55
|
+
|
|
56
|
+
# Install the CLI (pipx recommended)
|
|
57
|
+
pipx install intent-cli-python
|
|
58
|
+
|
|
59
|
+
# Or using pip
|
|
46
60
|
pip install intent-cli-python
|
|
47
61
|
```
|
|
48
62
|
|
|
49
63
|
Requires Python 3.9+ and Git.
|
|
50
64
|
|
|
65
|
+
### Add the Claude Code skill
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npx skills add dozybot001/Intent
|
|
69
|
+
```
|
|
70
|
+
|
|
51
71
|
## Quick start
|
|
52
72
|
|
|
53
73
|
```bash
|
|
@@ -136,6 +156,12 @@ All data lives in `.intent/` at your git repo root:
|
|
|
136
156
|
decision-001.json
|
|
137
157
|
```
|
|
138
158
|
|
|
159
|
+
## Docs
|
|
160
|
+
|
|
161
|
+
- [Vision](docs/EN/vision.md) — why semantic history matters. **If this project interests you, start here.**
|
|
162
|
+
- [CLI Design](docs/EN/cli.md) — object model, commands, JSON contract
|
|
163
|
+
- [Roadmap](docs/EN/roadmap.md) — phase plan
|
|
164
|
+
|
|
139
165
|
## License
|
|
140
166
|
|
|
141
167
|
MIT
|
{intent_cli_python-0.6.0 → intent_cli_python-1.0.0}/src/intent_cli_python.egg-info/SOURCES.txt
RENAMED
|
@@ -10,4 +10,5 @@ src/intent_cli_python.egg-info/PKG-INFO
|
|
|
10
10
|
src/intent_cli_python.egg-info/SOURCES.txt
|
|
11
11
|
src/intent_cli_python.egg-info/dependency_links.txt
|
|
12
12
|
src/intent_cli_python.egg-info/entry_points.txt
|
|
13
|
-
src/intent_cli_python.egg-info/top_level.txt
|
|
13
|
+
src/intent_cli_python.egg-info/top_level.txt
|
|
14
|
+
tests/test_cli.py
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"""Tests for Intent CLI — covers all 19 commands, state machines, and error codes."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
import tempfile
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.fixture
|
|
13
|
+
def workspace(tmp_path):
|
|
14
|
+
"""Create a git repo with .intent/ initialized."""
|
|
15
|
+
subprocess.run(["git", "init"], cwd=tmp_path, capture_output=True, check=True)
|
|
16
|
+
subprocess.run(
|
|
17
|
+
["git", "commit", "--allow-empty", "-m", "init"],
|
|
18
|
+
cwd=tmp_path, capture_output=True, check=True,
|
|
19
|
+
)
|
|
20
|
+
result = _run(tmp_path, "init")
|
|
21
|
+
assert result["ok"] is True
|
|
22
|
+
return tmp_path
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _run(cwd, *args):
|
|
26
|
+
"""Run itt command and return parsed JSON."""
|
|
27
|
+
r = subprocess.run(
|
|
28
|
+
["itt", *args],
|
|
29
|
+
cwd=cwd, capture_output=True, text=True,
|
|
30
|
+
)
|
|
31
|
+
return json.loads(r.stdout)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
# Global commands
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
class TestGlobal:
|
|
39
|
+
def test_version(self, workspace):
|
|
40
|
+
r = _run(workspace, "version")
|
|
41
|
+
assert r["ok"] is True
|
|
42
|
+
assert "version" in r["result"]
|
|
43
|
+
|
|
44
|
+
def test_init_already_exists(self, workspace):
|
|
45
|
+
r = _run(workspace, "init")
|
|
46
|
+
assert r["ok"] is False
|
|
47
|
+
assert r["error"]["code"] == "ALREADY_EXISTS"
|
|
48
|
+
|
|
49
|
+
def test_init_not_git(self, tmp_path):
|
|
50
|
+
r = _run(tmp_path, "init")
|
|
51
|
+
assert r["ok"] is False
|
|
52
|
+
assert r["error"]["code"] == "GIT_STATE_INVALID"
|
|
53
|
+
|
|
54
|
+
def test_inspect_empty(self, workspace):
|
|
55
|
+
r = _run(workspace, "inspect")
|
|
56
|
+
assert r["ok"] is True
|
|
57
|
+
assert r["active_intents"] == []
|
|
58
|
+
assert r["active_decisions"] == []
|
|
59
|
+
assert r["recent_snaps"] == []
|
|
60
|
+
|
|
61
|
+
def test_not_initialized(self, tmp_path):
|
|
62
|
+
subprocess.run(["git", "init"], cwd=tmp_path, capture_output=True, check=True)
|
|
63
|
+
subprocess.run(
|
|
64
|
+
["git", "commit", "--allow-empty", "-m", "init"],
|
|
65
|
+
cwd=tmp_path, capture_output=True, check=True,
|
|
66
|
+
)
|
|
67
|
+
r = _run(tmp_path, "inspect")
|
|
68
|
+
assert r["ok"] is False
|
|
69
|
+
assert r["error"]["code"] == "NOT_INITIALIZED"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# ---------------------------------------------------------------------------
|
|
73
|
+
# Intent commands
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
class TestIntent:
|
|
77
|
+
def test_create(self, workspace):
|
|
78
|
+
r = _run(workspace, "intent", "create", "Fix bug", "--query", "why crash?")
|
|
79
|
+
assert r["ok"] is True
|
|
80
|
+
assert r["result"]["id"] == "intent-001"
|
|
81
|
+
assert r["result"]["status"] == "active"
|
|
82
|
+
assert r["result"]["source_query"] == "why crash?"
|
|
83
|
+
|
|
84
|
+
def test_create_auto_attaches_decisions(self, workspace):
|
|
85
|
+
_run(workspace, "intent", "create", "Goal A", "--query", "q")
|
|
86
|
+
_run(workspace, "decision", "create", "Rule 1", "--rationale", "r")
|
|
87
|
+
r = _run(workspace, "intent", "create", "Goal B", "--query", "q")
|
|
88
|
+
assert "decision-001" in r["result"]["decision_ids"]
|
|
89
|
+
|
|
90
|
+
def test_list(self, workspace):
|
|
91
|
+
_run(workspace, "intent", "create", "A", "--query", "q")
|
|
92
|
+
_run(workspace, "intent", "create", "B", "--query", "q")
|
|
93
|
+
r = _run(workspace, "intent", "list")
|
|
94
|
+
assert len(r["result"]) == 2
|
|
95
|
+
|
|
96
|
+
def test_list_filter_status(self, workspace):
|
|
97
|
+
_run(workspace, "intent", "create", "A", "--query", "q")
|
|
98
|
+
_run(workspace, "intent", "create", "B", "--query", "q")
|
|
99
|
+
_run(workspace, "intent", "suspend", "intent-001")
|
|
100
|
+
r = _run(workspace, "intent", "list", "--status", "active")
|
|
101
|
+
assert len(r["result"]) == 1
|
|
102
|
+
assert r["result"][0]["id"] == "intent-002"
|
|
103
|
+
|
|
104
|
+
def test_show(self, workspace):
|
|
105
|
+
_run(workspace, "intent", "create", "A", "--query", "q")
|
|
106
|
+
r = _run(workspace, "intent", "show", "intent-001")
|
|
107
|
+
assert r["result"]["title"] == "A"
|
|
108
|
+
|
|
109
|
+
def test_show_not_found(self, workspace):
|
|
110
|
+
r = _run(workspace, "intent", "show", "intent-999")
|
|
111
|
+
assert r["error"]["code"] == "OBJECT_NOT_FOUND"
|
|
112
|
+
|
|
113
|
+
def test_suspend_activate(self, workspace):
|
|
114
|
+
_run(workspace, "intent", "create", "A", "--query", "q")
|
|
115
|
+
r = _run(workspace, "intent", "suspend", "intent-001")
|
|
116
|
+
assert r["result"]["status"] == "suspend"
|
|
117
|
+
r = _run(workspace, "intent", "activate", "intent-001")
|
|
118
|
+
assert r["result"]["status"] == "active"
|
|
119
|
+
|
|
120
|
+
def test_activate_catches_up_decisions(self, workspace):
|
|
121
|
+
_run(workspace, "intent", "create", "A", "--query", "q")
|
|
122
|
+
_run(workspace, "intent", "suspend", "intent-001")
|
|
123
|
+
_run(workspace, "decision", "create", "New rule", "--rationale", "r")
|
|
124
|
+
r = _run(workspace, "intent", "activate", "intent-001")
|
|
125
|
+
assert "decision-001" in r["result"]["decision_ids"]
|
|
126
|
+
|
|
127
|
+
def test_done(self, workspace):
|
|
128
|
+
_run(workspace, "intent", "create", "A", "--query", "q")
|
|
129
|
+
r = _run(workspace, "intent", "done", "intent-001")
|
|
130
|
+
assert r["result"]["status"] == "done"
|
|
131
|
+
|
|
132
|
+
def test_done_is_terminal(self, workspace):
|
|
133
|
+
_run(workspace, "intent", "create", "A", "--query", "q")
|
|
134
|
+
_run(workspace, "intent", "done", "intent-001")
|
|
135
|
+
r = _run(workspace, "intent", "activate", "intent-001")
|
|
136
|
+
assert r["error"]["code"] == "STATE_CONFLICT"
|
|
137
|
+
|
|
138
|
+
def test_suspend_only_active(self, workspace):
|
|
139
|
+
_run(workspace, "intent", "create", "A", "--query", "q")
|
|
140
|
+
_run(workspace, "intent", "done", "intent-001")
|
|
141
|
+
r = _run(workspace, "intent", "suspend", "intent-001")
|
|
142
|
+
assert r["error"]["code"] == "STATE_CONFLICT"
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# ---------------------------------------------------------------------------
|
|
146
|
+
# Snap commands
|
|
147
|
+
# ---------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
class TestSnap:
|
|
150
|
+
def test_create(self, workspace):
|
|
151
|
+
_run(workspace, "intent", "create", "Goal", "--query", "q")
|
|
152
|
+
r = _run(workspace, "snap", "create", "Did X", "--intent", "intent-001",
|
|
153
|
+
"--summary", "details")
|
|
154
|
+
assert r["ok"] is True
|
|
155
|
+
assert r["result"]["id"] == "snap-001"
|
|
156
|
+
assert r["result"]["intent_id"] == "intent-001"
|
|
157
|
+
|
|
158
|
+
def test_create_updates_intent_snap_ids(self, workspace):
|
|
159
|
+
_run(workspace, "intent", "create", "Goal", "--query", "q")
|
|
160
|
+
_run(workspace, "snap", "create", "S1", "--intent", "intent-001")
|
|
161
|
+
_run(workspace, "snap", "create", "S2", "--intent", "intent-001")
|
|
162
|
+
r = _run(workspace, "intent", "show", "intent-001")
|
|
163
|
+
assert r["result"]["snap_ids"] == ["snap-001", "snap-002"]
|
|
164
|
+
|
|
165
|
+
def test_create_requires_active_intent(self, workspace):
|
|
166
|
+
_run(workspace, "intent", "create", "Goal", "--query", "q")
|
|
167
|
+
_run(workspace, "intent", "done", "intent-001")
|
|
168
|
+
r = _run(workspace, "snap", "create", "S", "--intent", "intent-001")
|
|
169
|
+
assert r["error"]["code"] == "STATE_CONFLICT"
|
|
170
|
+
|
|
171
|
+
def test_create_intent_not_found(self, workspace):
|
|
172
|
+
r = _run(workspace, "snap", "create", "S", "--intent", "intent-999")
|
|
173
|
+
assert r["error"]["code"] == "OBJECT_NOT_FOUND"
|
|
174
|
+
|
|
175
|
+
def test_list(self, workspace):
|
|
176
|
+
_run(workspace, "intent", "create", "Goal", "--query", "q")
|
|
177
|
+
_run(workspace, "snap", "create", "S1", "--intent", "intent-001")
|
|
178
|
+
_run(workspace, "snap", "create", "S2", "--intent", "intent-001")
|
|
179
|
+
r = _run(workspace, "snap", "list")
|
|
180
|
+
assert len(r["result"]) == 2
|
|
181
|
+
|
|
182
|
+
def test_list_filter_intent(self, workspace):
|
|
183
|
+
_run(workspace, "intent", "create", "A", "--query", "q")
|
|
184
|
+
_run(workspace, "intent", "create", "B", "--query", "q")
|
|
185
|
+
_run(workspace, "snap", "create", "S1", "--intent", "intent-001")
|
|
186
|
+
_run(workspace, "snap", "create", "S2", "--intent", "intent-002")
|
|
187
|
+
r = _run(workspace, "snap", "list", "--intent", "intent-002")
|
|
188
|
+
assert len(r["result"]) == 1
|
|
189
|
+
assert r["result"][0]["id"] == "snap-002"
|
|
190
|
+
|
|
191
|
+
def test_feedback(self, workspace):
|
|
192
|
+
_run(workspace, "intent", "create", "Goal", "--query", "q")
|
|
193
|
+
_run(workspace, "snap", "create", "S", "--intent", "intent-001")
|
|
194
|
+
r = _run(workspace, "snap", "feedback", "snap-001", "looks good")
|
|
195
|
+
assert r["result"]["feedback"] == "looks good"
|
|
196
|
+
|
|
197
|
+
def test_feedback_overwrites(self, workspace):
|
|
198
|
+
_run(workspace, "intent", "create", "Goal", "--query", "q")
|
|
199
|
+
_run(workspace, "snap", "create", "S", "--intent", "intent-001")
|
|
200
|
+
_run(workspace, "snap", "feedback", "snap-001", "first")
|
|
201
|
+
r = _run(workspace, "snap", "feedback", "snap-001", "second")
|
|
202
|
+
assert r["result"]["feedback"] == "second"
|
|
203
|
+
|
|
204
|
+
def test_revert(self, workspace):
|
|
205
|
+
_run(workspace, "intent", "create", "Goal", "--query", "q")
|
|
206
|
+
_run(workspace, "snap", "create", "S", "--intent", "intent-001")
|
|
207
|
+
r = _run(workspace, "snap", "revert", "snap-001")
|
|
208
|
+
assert r["result"]["status"] == "reverted"
|
|
209
|
+
|
|
210
|
+
def test_revert_is_terminal(self, workspace):
|
|
211
|
+
_run(workspace, "intent", "create", "Goal", "--query", "q")
|
|
212
|
+
_run(workspace, "snap", "create", "S", "--intent", "intent-001")
|
|
213
|
+
_run(workspace, "snap", "revert", "snap-001")
|
|
214
|
+
r = _run(workspace, "snap", "revert", "snap-001")
|
|
215
|
+
assert r["error"]["code"] == "STATE_CONFLICT"
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
# ---------------------------------------------------------------------------
|
|
219
|
+
# Decision commands
|
|
220
|
+
# ---------------------------------------------------------------------------
|
|
221
|
+
|
|
222
|
+
class TestDecision:
|
|
223
|
+
def test_create(self, workspace):
|
|
224
|
+
r = _run(workspace, "decision", "create", "Rule", "--rationale", "reason")
|
|
225
|
+
assert r["ok"] is True
|
|
226
|
+
assert r["result"]["id"] == "decision-001"
|
|
227
|
+
assert r["result"]["status"] == "active"
|
|
228
|
+
|
|
229
|
+
def test_create_auto_attaches_intents(self, workspace):
|
|
230
|
+
_run(workspace, "intent", "create", "A", "--query", "q")
|
|
231
|
+
r = _run(workspace, "decision", "create", "Rule", "--rationale", "r")
|
|
232
|
+
assert "intent-001" in r["result"]["intent_ids"]
|
|
233
|
+
# Verify bidirectional
|
|
234
|
+
i = _run(workspace, "intent", "show", "intent-001")
|
|
235
|
+
assert "decision-001" in i["result"]["decision_ids"]
|
|
236
|
+
|
|
237
|
+
def test_list(self, workspace):
|
|
238
|
+
_run(workspace, "decision", "create", "R1", "--rationale", "r")
|
|
239
|
+
_run(workspace, "decision", "create", "R2", "--rationale", "r")
|
|
240
|
+
r = _run(workspace, "decision", "list")
|
|
241
|
+
assert len(r["result"]) == 2
|
|
242
|
+
|
|
243
|
+
def test_deprecate(self, workspace):
|
|
244
|
+
_run(workspace, "decision", "create", "R", "--rationale", "r")
|
|
245
|
+
r = _run(workspace, "decision", "deprecate", "decision-001")
|
|
246
|
+
assert r["result"]["status"] == "deprecated"
|
|
247
|
+
|
|
248
|
+
def test_deprecate_is_terminal(self, workspace):
|
|
249
|
+
_run(workspace, "decision", "create", "R", "--rationale", "r")
|
|
250
|
+
_run(workspace, "decision", "deprecate", "decision-001")
|
|
251
|
+
r = _run(workspace, "decision", "deprecate", "decision-001")
|
|
252
|
+
assert r["error"]["code"] == "STATE_CONFLICT"
|
|
253
|
+
|
|
254
|
+
def test_deprecated_not_auto_attached(self, workspace):
|
|
255
|
+
_run(workspace, "decision", "create", "R", "--rationale", "r")
|
|
256
|
+
_run(workspace, "decision", "deprecate", "decision-001")
|
|
257
|
+
r = _run(workspace, "intent", "create", "New goal", "--query", "q")
|
|
258
|
+
assert "decision-001" not in r["result"]["decision_ids"]
|
|
259
|
+
|
|
260
|
+
def test_attach(self, workspace):
|
|
261
|
+
_run(workspace, "intent", "create", "A", "--query", "q")
|
|
262
|
+
_run(workspace, "intent", "create", "B", "--query", "q")
|
|
263
|
+
_run(workspace, "decision", "create", "R", "--rationale", "r")
|
|
264
|
+
# decision-001 auto-attached to both. Manually attach to verify idempotency.
|
|
265
|
+
r = _run(workspace, "decision", "attach", "decision-001", "--intent", "intent-001")
|
|
266
|
+
assert r["ok"] is True
|
|
267
|
+
|
|
268
|
+
def test_attach_not_found(self, workspace):
|
|
269
|
+
_run(workspace, "decision", "create", "R", "--rationale", "r")
|
|
270
|
+
r = _run(workspace, "decision", "attach", "decision-001", "--intent", "intent-999")
|
|
271
|
+
assert r["error"]["code"] == "OBJECT_NOT_FOUND"
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
# ---------------------------------------------------------------------------
|
|
275
|
+
# Inspect
|
|
276
|
+
# ---------------------------------------------------------------------------
|
|
277
|
+
|
|
278
|
+
class TestInspect:
|
|
279
|
+
def test_full_graph(self, workspace):
|
|
280
|
+
_run(workspace, "intent", "create", "Active", "--query", "q")
|
|
281
|
+
_run(workspace, "intent", "create", "Will suspend", "--query", "q")
|
|
282
|
+
_run(workspace, "intent", "suspend", "intent-002")
|
|
283
|
+
_run(workspace, "decision", "create", "Rule", "--rationale", "r")
|
|
284
|
+
_run(workspace, "snap", "create", "S1", "--intent", "intent-001",
|
|
285
|
+
"--summary", "did something")
|
|
286
|
+
|
|
287
|
+
r = _run(workspace, "inspect")
|
|
288
|
+
assert r["ok"] is True
|
|
289
|
+
assert len(r["active_intents"]) == 1
|
|
290
|
+
assert r["active_intents"][0]["id"] == "intent-001"
|
|
291
|
+
assert r["active_intents"][0]["latest_snap_id"] == "snap-001"
|
|
292
|
+
assert len(r["suspend_intents"]) == 1
|
|
293
|
+
assert r["suspend_intents"][0]["id"] == "intent-002"
|
|
294
|
+
assert len(r["active_decisions"]) == 1
|
|
295
|
+
assert len(r["recent_snaps"]) == 1
|
|
296
|
+
|
|
297
|
+
def test_orphan_snap_warning(self, workspace):
|
|
298
|
+
_run(workspace, "intent", "create", "Goal", "--query", "q")
|
|
299
|
+
_run(workspace, "snap", "create", "S", "--intent", "intent-001")
|
|
300
|
+
# Delete intent file to create orphan
|
|
301
|
+
intent_file = workspace / ".intent" / "intents" / "intent-001.json"
|
|
302
|
+
intent_file.unlink()
|
|
303
|
+
r = _run(workspace, "inspect")
|
|
304
|
+
assert any("Orphan" in w for w in r["warnings"])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{intent_cli_python-0.6.0 → intent_cli_python-1.0.0}/src/intent_cli_python.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{intent_cli_python-0.6.0 → intent_cli_python-1.0.0}/src/intent_cli_python.egg-info/top_level.txt
RENAMED
|
File without changes
|