intent-cli-python 1.1.0__tar.gz → 1.2.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-1.1.0 → intent_cli_python-1.2.0}/PKG-INFO +5 -1
- {intent_cli_python-1.1.0 → intent_cli_python-1.2.0}/README.md +4 -0
- {intent_cli_python-1.1.0 → intent_cli_python-1.2.0}/pyproject.toml +1 -1
- intent_cli_python-1.2.0/src/intent_cli/cli.py +178 -0
- intent_cli_python-1.2.0/src/intent_cli/commands/__init__.py +1 -0
- intent_cli_python-1.2.0/src/intent_cli/commands/common.py +41 -0
- intent_cli_python-1.2.0/src/intent_cli/commands/core.py +376 -0
- intent_cli_python-1.2.0/src/intent_cli/commands/hub.py +107 -0
- intent_cli_python-1.2.0/src/intent_cli/hub/__init__.py +1 -0
- intent_cli_python-1.2.0/src/intent_cli/hub/client.py +59 -0
- intent_cli_python-1.2.0/src/intent_cli/hub/payload.py +65 -0
- intent_cli_python-1.2.0/src/intent_cli/hub/runtime.py +40 -0
- {intent_cli_python-1.1.0 → intent_cli_python-1.2.0}/src/intent_cli/store.py +96 -0
- {intent_cli_python-1.1.0 → intent_cli_python-1.2.0}/src/intent_cli_python.egg-info/PKG-INFO +5 -1
- {intent_cli_python-1.1.0 → intent_cli_python-1.2.0}/src/intent_cli_python.egg-info/SOURCES.txt +8 -0
- {intent_cli_python-1.1.0 → intent_cli_python-1.2.0}/tests/test_cli.py +98 -0
- intent_cli_python-1.1.0/src/intent_cli/cli.py +0 -528
- {intent_cli_python-1.1.0 → intent_cli_python-1.2.0}/LICENSE +0 -0
- {intent_cli_python-1.1.0 → intent_cli_python-1.2.0}/setup.cfg +0 -0
- {intent_cli_python-1.1.0 → intent_cli_python-1.2.0}/src/intent_cli/__init__.py +0 -0
- {intent_cli_python-1.1.0 → intent_cli_python-1.2.0}/src/intent_cli/__main__.py +0 -0
- {intent_cli_python-1.1.0 → intent_cli_python-1.2.0}/src/intent_cli/output.py +0 -0
- {intent_cli_python-1.1.0 → intent_cli_python-1.2.0}/src/intent_cli_python.egg-info/dependency_links.txt +0 -0
- {intent_cli_python-1.1.0 → intent_cli_python-1.2.0}/src/intent_cli_python.egg-info/entry_points.txt +0 -0
- {intent_cli_python-1.1.0 → intent_cli_python-1.2.0}/src/intent_cli_python.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: intent-cli-python
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.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
|
|
@@ -173,11 +173,15 @@ All data lives in `.intent/` at your git repo root:
|
|
|
173
173
|
decision-001.json
|
|
174
174
|
```
|
|
175
175
|
|
|
176
|
+
`.intent/` is local semantic-workspace metadata. It should stay out of Git history and should remain ignored by `.gitignore`.
|
|
177
|
+
|
|
176
178
|
## Docs
|
|
177
179
|
|
|
178
180
|
- [Vision](docs/EN/vision.md) — why semantic history matters. **If this project interests you, start here.**
|
|
179
181
|
- [CLI Design](docs/EN/cli.md) — object model, commands, JSON contract
|
|
180
182
|
- [Roadmap](docs/EN/roadmap.md) — phase plan
|
|
183
|
+
- [IntHub MVP](docs/EN/inthub-mvp.md) — first remote collaboration-layer scope
|
|
184
|
+
- [IntHub Sync Contract](docs/EN/inthub-sync-contract.md) — first sync, identity, and API contract
|
|
181
185
|
|
|
182
186
|
## License
|
|
183
187
|
|
|
@@ -149,11 +149,15 @@ All data lives in `.intent/` at your git repo root:
|
|
|
149
149
|
decision-001.json
|
|
150
150
|
```
|
|
151
151
|
|
|
152
|
+
`.intent/` is local semantic-workspace metadata. It should stay out of Git history and should remain ignored by `.gitignore`.
|
|
153
|
+
|
|
152
154
|
## Docs
|
|
153
155
|
|
|
154
156
|
- [Vision](docs/EN/vision.md) — why semantic history matters. **If this project interests you, start here.**
|
|
155
157
|
- [CLI Design](docs/EN/cli.md) — object model, commands, JSON contract
|
|
156
158
|
- [Roadmap](docs/EN/roadmap.md) — phase plan
|
|
159
|
+
- [IntHub MVP](docs/EN/inthub-mvp.md) — first remote collaboration-layer scope
|
|
160
|
+
- [IntHub Sync Contract](docs/EN/inthub-sync-contract.md) — first sync, identity, and API contract
|
|
157
161
|
|
|
158
162
|
## License
|
|
159
163
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "intent-cli-python"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.2.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"
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""Intent CLI — parser and command dispatch."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
from intent_cli.commands.core import (
|
|
7
|
+
cmd_decision_attach,
|
|
8
|
+
cmd_decision_create,
|
|
9
|
+
cmd_decision_deprecate,
|
|
10
|
+
cmd_decision_list,
|
|
11
|
+
cmd_decision_show,
|
|
12
|
+
cmd_doctor,
|
|
13
|
+
cmd_init,
|
|
14
|
+
cmd_inspect,
|
|
15
|
+
cmd_intent_activate,
|
|
16
|
+
cmd_intent_create,
|
|
17
|
+
cmd_intent_done,
|
|
18
|
+
cmd_intent_list,
|
|
19
|
+
cmd_intent_show,
|
|
20
|
+
cmd_intent_suspend,
|
|
21
|
+
cmd_snap_create,
|
|
22
|
+
cmd_snap_feedback,
|
|
23
|
+
cmd_snap_list,
|
|
24
|
+
cmd_snap_revert,
|
|
25
|
+
cmd_snap_show,
|
|
26
|
+
cmd_version,
|
|
27
|
+
)
|
|
28
|
+
from intent_cli.commands.hub import cmd_hub_link, cmd_hub_login, cmd_hub_sync
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def main():
|
|
32
|
+
parser = argparse.ArgumentParser(prog="itt", description="Intent CLI")
|
|
33
|
+
sub = parser.add_subparsers(dest="command")
|
|
34
|
+
|
|
35
|
+
# version / init / inspect / doctor
|
|
36
|
+
sub.add_parser("version")
|
|
37
|
+
sub.add_parser("init")
|
|
38
|
+
sub.add_parser("inspect")
|
|
39
|
+
sub.add_parser("doctor")
|
|
40
|
+
|
|
41
|
+
# --- hub ---
|
|
42
|
+
p_hub = sub.add_parser("hub")
|
|
43
|
+
s_hub = p_hub.add_subparsers(dest="sub")
|
|
44
|
+
|
|
45
|
+
p = s_hub.add_parser("login")
|
|
46
|
+
p.add_argument("--api-base-url", default=None)
|
|
47
|
+
p.add_argument("--token", default=None)
|
|
48
|
+
|
|
49
|
+
p = s_hub.add_parser("link")
|
|
50
|
+
p.add_argument("--project-name", default=None)
|
|
51
|
+
p.add_argument("--api-base-url", default=None)
|
|
52
|
+
p.add_argument("--token", default=None)
|
|
53
|
+
|
|
54
|
+
p = s_hub.add_parser("sync")
|
|
55
|
+
p.add_argument("--api-base-url", default=None)
|
|
56
|
+
p.add_argument("--token", default=None)
|
|
57
|
+
p.add_argument("--dry-run", action="store_true")
|
|
58
|
+
|
|
59
|
+
# --- intent ---
|
|
60
|
+
p_intent = sub.add_parser("intent")
|
|
61
|
+
s_intent = p_intent.add_subparsers(dest="sub")
|
|
62
|
+
|
|
63
|
+
p = s_intent.add_parser("create")
|
|
64
|
+
p.add_argument("title")
|
|
65
|
+
p.add_argument("--query", default="")
|
|
66
|
+
p.add_argument("--rationale", default="")
|
|
67
|
+
|
|
68
|
+
p = s_intent.add_parser("list")
|
|
69
|
+
p.add_argument("--status", default=None)
|
|
70
|
+
p.add_argument("--decision", default=None)
|
|
71
|
+
|
|
72
|
+
p = s_intent.add_parser("show")
|
|
73
|
+
p.add_argument("id")
|
|
74
|
+
|
|
75
|
+
p = s_intent.add_parser("activate")
|
|
76
|
+
p.add_argument("id")
|
|
77
|
+
|
|
78
|
+
p = s_intent.add_parser("suspend")
|
|
79
|
+
p.add_argument("id")
|
|
80
|
+
|
|
81
|
+
p = s_intent.add_parser("done")
|
|
82
|
+
p.add_argument("id")
|
|
83
|
+
|
|
84
|
+
# --- snap ---
|
|
85
|
+
p_snap = sub.add_parser("snap")
|
|
86
|
+
s_snap = p_snap.add_subparsers(dest="sub")
|
|
87
|
+
|
|
88
|
+
p = s_snap.add_parser("create")
|
|
89
|
+
p.add_argument("title")
|
|
90
|
+
p.add_argument("--intent", required=True)
|
|
91
|
+
p.add_argument("--query", default="")
|
|
92
|
+
p.add_argument("--rationale", default="")
|
|
93
|
+
p.add_argument("--summary", default="")
|
|
94
|
+
p.add_argument("--feedback", default="")
|
|
95
|
+
|
|
96
|
+
p = s_snap.add_parser("list")
|
|
97
|
+
p.add_argument("--intent", default=None)
|
|
98
|
+
p.add_argument("--status", default=None)
|
|
99
|
+
|
|
100
|
+
p = s_snap.add_parser("show")
|
|
101
|
+
p.add_argument("id")
|
|
102
|
+
|
|
103
|
+
p = s_snap.add_parser("feedback")
|
|
104
|
+
p.add_argument("id")
|
|
105
|
+
p.add_argument("feedback")
|
|
106
|
+
|
|
107
|
+
p = s_snap.add_parser("revert")
|
|
108
|
+
p.add_argument("id")
|
|
109
|
+
|
|
110
|
+
# --- decision ---
|
|
111
|
+
p_decision = sub.add_parser("decision")
|
|
112
|
+
s_decision = p_decision.add_subparsers(dest="sub")
|
|
113
|
+
|
|
114
|
+
p = s_decision.add_parser("create")
|
|
115
|
+
p.add_argument("title")
|
|
116
|
+
p.add_argument("--rationale", default="")
|
|
117
|
+
|
|
118
|
+
p = s_decision.add_parser("list")
|
|
119
|
+
p.add_argument("--status", default=None)
|
|
120
|
+
p.add_argument("--intent", default=None)
|
|
121
|
+
|
|
122
|
+
p = s_decision.add_parser("show")
|
|
123
|
+
p.add_argument("id")
|
|
124
|
+
|
|
125
|
+
p = s_decision.add_parser("deprecate")
|
|
126
|
+
p.add_argument("id")
|
|
127
|
+
|
|
128
|
+
p = s_decision.add_parser("attach")
|
|
129
|
+
p.add_argument("id")
|
|
130
|
+
p.add_argument("--intent", required=True)
|
|
131
|
+
|
|
132
|
+
args = parser.parse_args()
|
|
133
|
+
|
|
134
|
+
if args.command is None:
|
|
135
|
+
parser.print_help()
|
|
136
|
+
sys.exit(1)
|
|
137
|
+
|
|
138
|
+
dispatch_global = {
|
|
139
|
+
"version": cmd_version,
|
|
140
|
+
"init": cmd_init,
|
|
141
|
+
"inspect": cmd_inspect,
|
|
142
|
+
"doctor": cmd_doctor,
|
|
143
|
+
}
|
|
144
|
+
if args.command in dispatch_global:
|
|
145
|
+
dispatch_global[args.command](args)
|
|
146
|
+
return
|
|
147
|
+
|
|
148
|
+
if not getattr(args, "sub", None):
|
|
149
|
+
{
|
|
150
|
+
"hub": p_hub,
|
|
151
|
+
"intent": p_intent,
|
|
152
|
+
"snap": p_snap,
|
|
153
|
+
"decision": p_decision,
|
|
154
|
+
}[args.command].print_help()
|
|
155
|
+
sys.exit(1)
|
|
156
|
+
|
|
157
|
+
dispatch = {
|
|
158
|
+
("hub", "login"): cmd_hub_login,
|
|
159
|
+
("hub", "link"): cmd_hub_link,
|
|
160
|
+
("hub", "sync"): cmd_hub_sync,
|
|
161
|
+
("intent", "create"): cmd_intent_create,
|
|
162
|
+
("intent", "list"): cmd_intent_list,
|
|
163
|
+
("intent", "show"): cmd_intent_show,
|
|
164
|
+
("intent", "activate"): cmd_intent_activate,
|
|
165
|
+
("intent", "suspend"): cmd_intent_suspend,
|
|
166
|
+
("intent", "done"): cmd_intent_done,
|
|
167
|
+
("snap", "create"): cmd_snap_create,
|
|
168
|
+
("snap", "list"): cmd_snap_list,
|
|
169
|
+
("snap", "show"): cmd_snap_show,
|
|
170
|
+
("snap", "feedback"): cmd_snap_feedback,
|
|
171
|
+
("snap", "revert"): cmd_snap_revert,
|
|
172
|
+
("decision", "create"): cmd_decision_create,
|
|
173
|
+
("decision", "list"): cmd_decision_list,
|
|
174
|
+
("decision", "show"): cmd_decision_show,
|
|
175
|
+
("decision", "deprecate"): cmd_decision_deprecate,
|
|
176
|
+
("decision", "attach"): cmd_decision_attach,
|
|
177
|
+
}
|
|
178
|
+
dispatch[(args.command, args.sub)](args)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Command handlers for the Intent CLI."""
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Shared helpers for CLI command handlers."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
|
|
5
|
+
from intent_cli.output import error
|
|
6
|
+
from intent_cli.store import VALID_STATUSES, ensure_init, git_root
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def now_utc():
|
|
10
|
+
return datetime.now(timezone.utc).isoformat()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def require_init():
|
|
14
|
+
"""Return .intent/ base path, or exit with a structured error."""
|
|
15
|
+
base = ensure_init()
|
|
16
|
+
if base is not None:
|
|
17
|
+
return base
|
|
18
|
+
if git_root() is None:
|
|
19
|
+
error(
|
|
20
|
+
"GIT_STATE_INVALID",
|
|
21
|
+
"Not inside a Git repository.",
|
|
22
|
+
suggested_fix="cd into a git repo and run: itt init",
|
|
23
|
+
)
|
|
24
|
+
error(
|
|
25
|
+
"NOT_INITIALIZED",
|
|
26
|
+
".intent/ directory not found.",
|
|
27
|
+
suggested_fix="itt init",
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def validate_status_filter(object_type, status):
|
|
32
|
+
"""Validate a --status filter against the object's state machine."""
|
|
33
|
+
if status is None:
|
|
34
|
+
return
|
|
35
|
+
allowed = sorted(VALID_STATUSES[object_type])
|
|
36
|
+
if status not in allowed:
|
|
37
|
+
error(
|
|
38
|
+
"INVALID_INPUT",
|
|
39
|
+
f"Invalid status '{status}' for {object_type}. Allowed values: {', '.join(allowed)}.",
|
|
40
|
+
suggested_fix=f"Use one of: {', '.join(allowed)}",
|
|
41
|
+
)
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
"""Core object command handlers for the Intent CLI."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from intent_cli import __version__
|
|
6
|
+
from intent_cli.commands.common import now_utc, require_init, validate_status_filter
|
|
7
|
+
from intent_cli.output import error, success
|
|
8
|
+
from intent_cli.store import (
|
|
9
|
+
VALID_STATUSES,
|
|
10
|
+
git_root,
|
|
11
|
+
init_workspace,
|
|
12
|
+
list_objects,
|
|
13
|
+
next_id,
|
|
14
|
+
read_config,
|
|
15
|
+
read_object,
|
|
16
|
+
validate_graph,
|
|
17
|
+
write_object,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def cmd_version(_args):
|
|
22
|
+
success("version", {"version": __version__})
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def cmd_init(_args):
|
|
26
|
+
path, err = init_workspace()
|
|
27
|
+
if err == "GIT_STATE_INVALID":
|
|
28
|
+
error(
|
|
29
|
+
"GIT_STATE_INVALID",
|
|
30
|
+
"Not inside a Git repository.",
|
|
31
|
+
suggested_fix="cd into a git repo and run: itt init",
|
|
32
|
+
)
|
|
33
|
+
if err == "ALREADY_EXISTS":
|
|
34
|
+
error(
|
|
35
|
+
"ALREADY_EXISTS",
|
|
36
|
+
".intent/ already exists.",
|
|
37
|
+
suggested_fix="Remove .intent/ first if you want to reinitialize.",
|
|
38
|
+
)
|
|
39
|
+
success("init", {"path": str(path)})
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def cmd_inspect(_args):
|
|
43
|
+
base = require_init()
|
|
44
|
+
config = read_config(base)
|
|
45
|
+
|
|
46
|
+
active_intents = []
|
|
47
|
+
suspend_intents = []
|
|
48
|
+
for obj in list_objects(base, "intent"):
|
|
49
|
+
entry = {
|
|
50
|
+
"id": obj["id"],
|
|
51
|
+
"title": obj["title"],
|
|
52
|
+
"status": obj["status"],
|
|
53
|
+
"decision_ids": obj.get("decision_ids", []),
|
|
54
|
+
"latest_snap_id": obj["snap_ids"][-1] if obj.get("snap_ids") else None,
|
|
55
|
+
}
|
|
56
|
+
if obj["status"] == "active":
|
|
57
|
+
active_intents.append(entry)
|
|
58
|
+
elif obj["status"] == "suspend":
|
|
59
|
+
suspend_intents.append(entry)
|
|
60
|
+
|
|
61
|
+
active_decisions = []
|
|
62
|
+
for obj in list_objects(base, "decision", status="active"):
|
|
63
|
+
active_decisions.append({
|
|
64
|
+
"id": obj["id"],
|
|
65
|
+
"title": obj["title"],
|
|
66
|
+
"status": obj["status"],
|
|
67
|
+
"intent_ids": obj.get("intent_ids", []),
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
all_snaps = list_objects(base, "snap")
|
|
71
|
+
all_snaps.sort(key=lambda s: s.get("created_at", ""), reverse=True)
|
|
72
|
+
recent_snaps = []
|
|
73
|
+
for snap in all_snaps[:10]:
|
|
74
|
+
recent_snaps.append({
|
|
75
|
+
"id": snap["id"],
|
|
76
|
+
"title": snap["title"],
|
|
77
|
+
"intent_id": snap["intent_id"],
|
|
78
|
+
"status": snap["status"],
|
|
79
|
+
"summary": snap.get("summary", ""),
|
|
80
|
+
"feedback": snap.get("feedback", ""),
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
warnings = []
|
|
84
|
+
intent_ids_on_disk = {obj["id"] for obj in list_objects(base, "intent")}
|
|
85
|
+
for snap in all_snaps:
|
|
86
|
+
if snap.get("intent_id") and snap["intent_id"] not in intent_ids_on_disk:
|
|
87
|
+
warnings.append(f"Orphan snap {snap['id']}: intent {snap['intent_id']} not found")
|
|
88
|
+
|
|
89
|
+
print(json.dumps({
|
|
90
|
+
"ok": True,
|
|
91
|
+
"schema_version": config.get("schema_version", "1.0"),
|
|
92
|
+
"active_intents": active_intents,
|
|
93
|
+
"suspend_intents": suspend_intents,
|
|
94
|
+
"active_decisions": active_decisions,
|
|
95
|
+
"recent_snaps": recent_snaps,
|
|
96
|
+
"warnings": warnings,
|
|
97
|
+
}, indent=2, ensure_ascii=False))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def cmd_doctor(_args):
|
|
101
|
+
base = require_init()
|
|
102
|
+
success("doctor", validate_graph(base))
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def cmd_intent_create(args):
|
|
106
|
+
base = require_init()
|
|
107
|
+
obj_id = next_id(base, "intent")
|
|
108
|
+
|
|
109
|
+
active_decisions = list_objects(base, "decision", status="active")
|
|
110
|
+
decision_ids = [decision["id"] for decision in active_decisions]
|
|
111
|
+
|
|
112
|
+
warnings = []
|
|
113
|
+
if not decision_ids:
|
|
114
|
+
warnings.append("No active decisions to attach.")
|
|
115
|
+
|
|
116
|
+
intent = {
|
|
117
|
+
"id": obj_id,
|
|
118
|
+
"object": "intent",
|
|
119
|
+
"created_at": now_utc(),
|
|
120
|
+
"title": args.title,
|
|
121
|
+
"status": "active",
|
|
122
|
+
"source_query": args.query,
|
|
123
|
+
"rationale": args.rationale,
|
|
124
|
+
"decision_ids": decision_ids,
|
|
125
|
+
"snap_ids": [],
|
|
126
|
+
}
|
|
127
|
+
write_object(base, "intent", obj_id, intent)
|
|
128
|
+
|
|
129
|
+
for decision in active_decisions:
|
|
130
|
+
if obj_id not in decision.get("intent_ids", []):
|
|
131
|
+
decision.setdefault("intent_ids", []).append(obj_id)
|
|
132
|
+
write_object(base, "decision", decision["id"], decision)
|
|
133
|
+
|
|
134
|
+
success("intent.create", intent, warnings)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def cmd_intent_list(args):
|
|
138
|
+
base = require_init()
|
|
139
|
+
validate_status_filter("intent", args.status)
|
|
140
|
+
objects = list_objects(base, "intent", status=args.status)
|
|
141
|
+
if args.decision:
|
|
142
|
+
objects = [obj for obj in objects if args.decision in obj.get("decision_ids", [])]
|
|
143
|
+
success("intent.list", objects)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def cmd_intent_show(args):
|
|
147
|
+
base = require_init()
|
|
148
|
+
obj = read_object(base, "intent", args.id)
|
|
149
|
+
if obj is None:
|
|
150
|
+
error("OBJECT_NOT_FOUND", f"Intent {args.id} not found.")
|
|
151
|
+
success("intent.show", obj)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def cmd_intent_activate(args):
|
|
155
|
+
base = require_init()
|
|
156
|
+
obj = read_object(base, "intent", args.id)
|
|
157
|
+
if obj is None:
|
|
158
|
+
error("OBJECT_NOT_FOUND", f"Intent {args.id} not found.")
|
|
159
|
+
if obj["status"] != "suspend":
|
|
160
|
+
error(
|
|
161
|
+
"STATE_CONFLICT",
|
|
162
|
+
f"Cannot activate intent with status '{obj['status']}'. Only 'suspend' intents can be activated.",
|
|
163
|
+
suggested_fix=f"itt intent show {args.id}",
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
obj["status"] = "active"
|
|
167
|
+
|
|
168
|
+
active_decisions = list_objects(base, "decision", status="active")
|
|
169
|
+
for decision in active_decisions:
|
|
170
|
+
if decision["id"] not in obj["decision_ids"]:
|
|
171
|
+
obj["decision_ids"].append(decision["id"])
|
|
172
|
+
if args.id not in decision.get("intent_ids", []):
|
|
173
|
+
decision.setdefault("intent_ids", []).append(args.id)
|
|
174
|
+
write_object(base, "decision", decision["id"], decision)
|
|
175
|
+
|
|
176
|
+
write_object(base, "intent", args.id, obj)
|
|
177
|
+
success("intent.activate", obj)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def cmd_intent_suspend(args):
|
|
181
|
+
base = require_init()
|
|
182
|
+
obj = read_object(base, "intent", args.id)
|
|
183
|
+
if obj is None:
|
|
184
|
+
error("OBJECT_NOT_FOUND", f"Intent {args.id} not found.")
|
|
185
|
+
if obj["status"] != "active":
|
|
186
|
+
error(
|
|
187
|
+
"STATE_CONFLICT",
|
|
188
|
+
f"Cannot suspend intent with status '{obj['status']}'. Only 'active' intents can be suspended.",
|
|
189
|
+
suggested_fix=f"itt intent show {args.id}",
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
obj["status"] = "suspend"
|
|
193
|
+
write_object(base, "intent", args.id, obj)
|
|
194
|
+
success("intent.suspend", obj)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def cmd_intent_done(args):
|
|
198
|
+
base = require_init()
|
|
199
|
+
obj = read_object(base, "intent", args.id)
|
|
200
|
+
if obj is None:
|
|
201
|
+
error("OBJECT_NOT_FOUND", f"Intent {args.id} not found.")
|
|
202
|
+
if obj["status"] != "active":
|
|
203
|
+
error(
|
|
204
|
+
"STATE_CONFLICT",
|
|
205
|
+
f"Cannot mark intent as done with status '{obj['status']}'. Only 'active' intents can be marked done.",
|
|
206
|
+
suggested_fix=f"itt intent show {args.id}",
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
obj["status"] = "done"
|
|
210
|
+
write_object(base, "intent", args.id, obj)
|
|
211
|
+
success("intent.done", obj)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def cmd_snap_create(args):
|
|
215
|
+
base = require_init()
|
|
216
|
+
intent_id = args.intent
|
|
217
|
+
|
|
218
|
+
intent = read_object(base, "intent", intent_id)
|
|
219
|
+
if intent is None:
|
|
220
|
+
error("OBJECT_NOT_FOUND", f"Intent {intent_id} not found.")
|
|
221
|
+
if intent["status"] != "active":
|
|
222
|
+
error(
|
|
223
|
+
"STATE_CONFLICT",
|
|
224
|
+
f"Cannot add snap to intent with status '{intent['status']}'. Only 'active' intents accept new snaps.",
|
|
225
|
+
suggested_fix=f"itt intent activate {intent_id}",
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
obj_id = next_id(base, "snap")
|
|
229
|
+
snap = {
|
|
230
|
+
"id": obj_id,
|
|
231
|
+
"object": "snap",
|
|
232
|
+
"created_at": now_utc(),
|
|
233
|
+
"title": args.title,
|
|
234
|
+
"status": "active",
|
|
235
|
+
"intent_id": intent_id,
|
|
236
|
+
"query": args.query,
|
|
237
|
+
"rationale": args.rationale,
|
|
238
|
+
"summary": args.summary,
|
|
239
|
+
"feedback": args.feedback,
|
|
240
|
+
}
|
|
241
|
+
write_object(base, "snap", obj_id, snap)
|
|
242
|
+
|
|
243
|
+
intent.setdefault("snap_ids", []).append(obj_id)
|
|
244
|
+
write_object(base, "intent", intent_id, intent)
|
|
245
|
+
|
|
246
|
+
success("snap.create", snap)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def cmd_snap_list(args):
|
|
250
|
+
base = require_init()
|
|
251
|
+
validate_status_filter("snap", args.status)
|
|
252
|
+
objects = list_objects(base, "snap", status=args.status)
|
|
253
|
+
if args.intent:
|
|
254
|
+
objects = [snap for snap in objects if snap.get("intent_id") == args.intent]
|
|
255
|
+
success("snap.list", objects)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def cmd_snap_show(args):
|
|
259
|
+
base = require_init()
|
|
260
|
+
obj = read_object(base, "snap", args.id)
|
|
261
|
+
if obj is None:
|
|
262
|
+
error("OBJECT_NOT_FOUND", f"Snap {args.id} not found.")
|
|
263
|
+
success("snap.show", obj)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def cmd_snap_feedback(args):
|
|
267
|
+
base = require_init()
|
|
268
|
+
obj = read_object(base, "snap", args.id)
|
|
269
|
+
if obj is None:
|
|
270
|
+
error("OBJECT_NOT_FOUND", f"Snap {args.id} not found.")
|
|
271
|
+
obj["feedback"] = args.feedback
|
|
272
|
+
write_object(base, "snap", args.id, obj)
|
|
273
|
+
success("snap.feedback", obj)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def cmd_snap_revert(args):
|
|
277
|
+
base = require_init()
|
|
278
|
+
obj = read_object(base, "snap", args.id)
|
|
279
|
+
if obj is None:
|
|
280
|
+
error("OBJECT_NOT_FOUND", f"Snap {args.id} not found.")
|
|
281
|
+
if obj["status"] != "active":
|
|
282
|
+
error(
|
|
283
|
+
"STATE_CONFLICT",
|
|
284
|
+
f"Cannot revert snap with status '{obj['status']}'. Only 'active' snaps can be reverted.",
|
|
285
|
+
suggested_fix=f"itt snap show {args.id}",
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
obj["status"] = "reverted"
|
|
289
|
+
write_object(base, "snap", args.id, obj)
|
|
290
|
+
success("snap.revert", obj)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def cmd_decision_create(args):
|
|
294
|
+
base = require_init()
|
|
295
|
+
obj_id = next_id(base, "decision")
|
|
296
|
+
|
|
297
|
+
active_intents = list_objects(base, "intent", status="active")
|
|
298
|
+
intent_ids = [intent["id"] for intent in active_intents]
|
|
299
|
+
|
|
300
|
+
warnings = []
|
|
301
|
+
if not intent_ids:
|
|
302
|
+
warnings.append("No active intents to attach.")
|
|
303
|
+
|
|
304
|
+
decision = {
|
|
305
|
+
"id": obj_id,
|
|
306
|
+
"object": "decision",
|
|
307
|
+
"created_at": now_utc(),
|
|
308
|
+
"title": args.title,
|
|
309
|
+
"status": "active",
|
|
310
|
+
"rationale": args.rationale,
|
|
311
|
+
"intent_ids": intent_ids,
|
|
312
|
+
}
|
|
313
|
+
write_object(base, "decision", obj_id, decision)
|
|
314
|
+
|
|
315
|
+
for intent in active_intents:
|
|
316
|
+
if obj_id not in intent.get("decision_ids", []):
|
|
317
|
+
intent.setdefault("decision_ids", []).append(obj_id)
|
|
318
|
+
write_object(base, "intent", intent["id"], intent)
|
|
319
|
+
|
|
320
|
+
success("decision.create", decision, warnings)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def cmd_decision_list(args):
|
|
324
|
+
base = require_init()
|
|
325
|
+
validate_status_filter("decision", args.status)
|
|
326
|
+
objects = list_objects(base, "decision", status=args.status)
|
|
327
|
+
if args.intent:
|
|
328
|
+
objects = [obj for obj in objects if args.intent in obj.get("intent_ids", [])]
|
|
329
|
+
success("decision.list", objects)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def cmd_decision_show(args):
|
|
333
|
+
base = require_init()
|
|
334
|
+
obj = read_object(base, "decision", args.id)
|
|
335
|
+
if obj is None:
|
|
336
|
+
error("OBJECT_NOT_FOUND", f"Decision {args.id} not found.")
|
|
337
|
+
success("decision.show", obj)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def cmd_decision_deprecate(args):
|
|
341
|
+
base = require_init()
|
|
342
|
+
obj = read_object(base, "decision", args.id)
|
|
343
|
+
if obj is None:
|
|
344
|
+
error("OBJECT_NOT_FOUND", f"Decision {args.id} not found.")
|
|
345
|
+
if obj["status"] != "active":
|
|
346
|
+
error(
|
|
347
|
+
"STATE_CONFLICT",
|
|
348
|
+
f"Cannot deprecate decision with status '{obj['status']}'. Only 'active' decisions can be deprecated.",
|
|
349
|
+
suggested_fix=f"itt decision show {args.id}",
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
obj["status"] = "deprecated"
|
|
353
|
+
write_object(base, "decision", args.id, obj)
|
|
354
|
+
success("decision.deprecate", obj)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def cmd_decision_attach(args):
|
|
358
|
+
base = require_init()
|
|
359
|
+
decision = read_object(base, "decision", args.id)
|
|
360
|
+
if decision is None:
|
|
361
|
+
error("OBJECT_NOT_FOUND", f"Decision {args.id} not found.")
|
|
362
|
+
|
|
363
|
+
intent_id = args.intent
|
|
364
|
+
intent = read_object(base, "intent", intent_id)
|
|
365
|
+
if intent is None:
|
|
366
|
+
error("OBJECT_NOT_FOUND", f"Intent {intent_id} not found.")
|
|
367
|
+
|
|
368
|
+
if intent_id not in decision.get("intent_ids", []):
|
|
369
|
+
decision.setdefault("intent_ids", []).append(intent_id)
|
|
370
|
+
write_object(base, "decision", args.id, decision)
|
|
371
|
+
|
|
372
|
+
if args.id not in intent.get("decision_ids", []):
|
|
373
|
+
intent.setdefault("decision_ids", []).append(args.id)
|
|
374
|
+
write_object(base, "intent", intent_id, intent)
|
|
375
|
+
|
|
376
|
+
success("decision.attach", decision)
|