xrefkit 0.4.0__tar.gz → 0.4.2__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.
- {xrefkit-0.4.0 → xrefkit-0.4.2}/PKG-INFO +17 -2
- {xrefkit-0.4.0 → xrefkit-0.4.2}/README.md +16 -1
- {xrefkit-0.4.0 → xrefkit-0.4.2}/pyproject.toml +1 -1
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_cli.py +22 -10
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_dashboard.py +2 -0
- xrefkit-0.4.2/tests/test_instruction_workflow.py +152 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_skill_runtime_audit.py +4 -1
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/__init__.py +1 -1
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/cli.py +2 -1
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/operations_cli.py +35 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/skillrun.py +185 -13
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit.egg-info/PKG-INFO +17 -2
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit.egg-info/SOURCES.txt +1 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/LICENSE +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/setup.cfg +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_base_sync_ownership.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_boundary_analysis.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_calibration_lint.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_check_skill_knowledge_xids.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_collect_analyzer_sarif.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_convert_to_xrefkit_skill.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_cs_scope_probe.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_csharp_commonality.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_csharp_naming_profile.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_ctx.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_cutover_readiness.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_error_policy_audit.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_error_policy_locator.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_fm_multiroot.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_gate.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_goal_desired_state.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_knowledge_relations_validator.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_ownership.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_packmeta.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_project_quality_baseline.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_resource_provider.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_runtime_contracts.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_sarif_to_locator.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_skillmeta.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_structure_catalog.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_xref.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_xrefkit_instance.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_xrefkit_tools.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_xrefkit_v2_discovery.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_xrefkit_v2_models.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/tests/test_xrefkit_v2_pipeline.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/__main__.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/boundary_analysis.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/catalog_cli.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/contracts.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/ctx.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/dashboard.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/discovery.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/gate.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/goalstate.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/hashing.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/import_skill.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/instance.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/loaders.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/mcp/__init__.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/mcp/audit.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/mcp/bootstrap.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/mcp/catalog.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/mcp/cli.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/mcp/client_cache.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/mcp/context_registry.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/mcp/contracts.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/mcp/dist.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/mcp/ownership.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/mcp/repository.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/mcp/schemas.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/mcp/server.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/mcp/startup_contract_pack.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/mcp_tools.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/models/__init__.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/models/common.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/models/effective_bundle.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/models/local_manifest.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/models/package_manifest.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/models/run_log.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/models/server_config.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/models/skill_definition.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/ownership.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/packmeta.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/registry.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/resolver.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/resource_provider.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/resources/base/contracts.json +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/resources/base/current.json +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/resources/base/generations/7a682a5272907354/contracts.json +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/resources/base/generations/7a682a5272907354/model_body.md +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/resources/base/generations/9929294385ccb7b0/contracts.json +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/resources/base/generations/9929294385ccb7b0/model_body.md +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/resources/base/model_body.md +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/runlog.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/skillmeta.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/structure_catalog.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/tools/__init__.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/tools/__main__.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/v2_cli.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/workspace.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/xref.py +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit.egg-info/dependency_links.txt +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit.egg-info/entry_points.txt +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit.egg-info/requires.txt +0 -0
- {xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xrefkit
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Portable XID, Skill, Knowledge, workflow, and MCP runtime for XRefKit
|
|
5
5
|
Author: synthaicode
|
|
6
6
|
License: MIT License
|
|
@@ -91,7 +91,7 @@ XRefKit makes AI work explicit by separating:
|
|
|
91
91
|
|
|
92
92
|
- Skills: executable work units, each identified by a capability/tuning/responsibility triad and carrying its execution and check contract
|
|
93
93
|
- Knowledge: source-backed domain facts and local rules loaded only when needed
|
|
94
|
-
- Workflow protocol: the generic, deterministic
|
|
94
|
+
- Workflow protocol: the generic, deterministic control for Skill-backed and instruction-backed runs (phases, verification, closure)
|
|
95
95
|
- Semantic routing: selecting the right Skill for a goal from user intent and the Skill catalog
|
|
96
96
|
- Evidence: logs, judgments, concerns, and quality checks
|
|
97
97
|
- XIDs: stable references that survive file movement and restructuring so AI can load targeted context without treating the whole repository as one prompt
|
|
@@ -109,6 +109,21 @@ and handoff records from collapsing into one opaque instruction block.
|
|
|
109
109
|
4. Agents are routed semantically to the right Skill and load only the relevant context.
|
|
110
110
|
5. Evidence and quality gates make incomplete or unsupported work visible.
|
|
111
111
|
|
|
112
|
+
When an instruction has no matching Skill, open an instruction-backed workflow
|
|
113
|
+
run explicitly. The run requires either user-supplied procedural completion
|
|
114
|
+
conditions or an explicit opt-in to the repository defaults:
|
|
115
|
+
|
|
116
|
+
```powershell
|
|
117
|
+
xrefkit workflow run --task "Perform the requested procedure" `
|
|
118
|
+
--use-default-completion-conditions --json
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`verify` and `close` determine procedural completion only. Output quality is
|
|
122
|
+
recorded separately after human acceptance with the existing feedback record.
|
|
123
|
+
Each work item also requires its own completion criterion; if that criterion is
|
|
124
|
+
not yet definable, record the item as unknown, blocked, or escalated with a
|
|
125
|
+
reason instead of inventing a criterion.
|
|
126
|
+
|
|
112
127
|
## Quick Start
|
|
113
128
|
|
|
114
129
|
Install the package and initialize an instance:
|
|
@@ -52,7 +52,7 @@ XRefKit makes AI work explicit by separating:
|
|
|
52
52
|
|
|
53
53
|
- Skills: executable work units, each identified by a capability/tuning/responsibility triad and carrying its execution and check contract
|
|
54
54
|
- Knowledge: source-backed domain facts and local rules loaded only when needed
|
|
55
|
-
- Workflow protocol: the generic, deterministic
|
|
55
|
+
- Workflow protocol: the generic, deterministic control for Skill-backed and instruction-backed runs (phases, verification, closure)
|
|
56
56
|
- Semantic routing: selecting the right Skill for a goal from user intent and the Skill catalog
|
|
57
57
|
- Evidence: logs, judgments, concerns, and quality checks
|
|
58
58
|
- XIDs: stable references that survive file movement and restructuring so AI can load targeted context without treating the whole repository as one prompt
|
|
@@ -70,6 +70,21 @@ and handoff records from collapsing into one opaque instruction block.
|
|
|
70
70
|
4. Agents are routed semantically to the right Skill and load only the relevant context.
|
|
71
71
|
5. Evidence and quality gates make incomplete or unsupported work visible.
|
|
72
72
|
|
|
73
|
+
When an instruction has no matching Skill, open an instruction-backed workflow
|
|
74
|
+
run explicitly. The run requires either user-supplied procedural completion
|
|
75
|
+
conditions or an explicit opt-in to the repository defaults:
|
|
76
|
+
|
|
77
|
+
```powershell
|
|
78
|
+
xrefkit workflow run --task "Perform the requested procedure" `
|
|
79
|
+
--use-default-completion-conditions --json
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`verify` and `close` determine procedural completion only. Output quality is
|
|
83
|
+
recorded separately after human acceptance with the existing feedback record.
|
|
84
|
+
Each work item also requires its own completion criterion; if that criterion is
|
|
85
|
+
not yet definable, record the item as unknown, blocked, or escalated with a
|
|
86
|
+
reason instead of inventing a criterion.
|
|
87
|
+
|
|
73
88
|
## Quick Start
|
|
74
89
|
|
|
75
90
|
Install the package and initialize an instance:
|
|
@@ -82,6 +82,8 @@ class CliTests(unittest.TestCase):
|
|
|
82
82
|
"WI-001",
|
|
83
83
|
"--text",
|
|
84
84
|
"Implement controlled output",
|
|
85
|
+
"--completion-criterion",
|
|
86
|
+
"output is written and validated",
|
|
85
87
|
"--status",
|
|
86
88
|
"done",
|
|
87
89
|
"--role",
|
|
@@ -739,6 +741,8 @@ class CliTests(unittest.TestCase):
|
|
|
739
741
|
"WI-001",
|
|
740
742
|
"--text",
|
|
741
743
|
"Implement controlled output",
|
|
744
|
+
"--completion-criterion",
|
|
745
|
+
"output is written and validated",
|
|
742
746
|
"--status",
|
|
743
747
|
"in_progress",
|
|
744
748
|
"--role",
|
|
@@ -765,6 +769,8 @@ class CliTests(unittest.TestCase):
|
|
|
765
769
|
"WI-001",
|
|
766
770
|
"--status",
|
|
767
771
|
"done",
|
|
772
|
+
"--completion-criterion",
|
|
773
|
+
"output is written and validated",
|
|
768
774
|
"--role",
|
|
769
775
|
"sample_skill:executor",
|
|
770
776
|
]
|
|
@@ -772,7 +778,7 @@ class CliTests(unittest.TestCase):
|
|
|
772
778
|
)
|
|
773
779
|
|
|
774
780
|
text = out.read_text(encoding="utf-8")
|
|
775
|
-
self.assertIn("
|
|
781
|
+
self.assertIn("WI-001 status=`done` role=`sample_skill:executor` criterion=`output is written and validated`", text)
|
|
776
782
|
|
|
777
783
|
def test_main_skill_close_rejects_pending_concrete_work_item(self) -> None:
|
|
778
784
|
with tempfile.TemporaryDirectory() as tmp:
|
|
@@ -808,9 +814,11 @@ class CliTests(unittest.TestCase):
|
|
|
808
814
|
str(out),
|
|
809
815
|
"--item",
|
|
810
816
|
"WI-001",
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
817
|
+
"--text",
|
|
818
|
+
"Implement controlled output",
|
|
819
|
+
"--completion-criterion",
|
|
820
|
+
"output is written and validated",
|
|
821
|
+
"--status",
|
|
814
822
|
"pending",
|
|
815
823
|
"--role",
|
|
816
824
|
"sample_skill:executor",
|
|
@@ -1065,9 +1073,11 @@ class CliTests(unittest.TestCase):
|
|
|
1065
1073
|
str(out),
|
|
1066
1074
|
"--item",
|
|
1067
1075
|
"WI-001",
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1076
|
+
"--text",
|
|
1077
|
+
"Implement controlled output",
|
|
1078
|
+
"--completion-criterion",
|
|
1079
|
+
"output is written and validated",
|
|
1080
|
+
"--status",
|
|
1071
1081
|
"done",
|
|
1072
1082
|
"--role",
|
|
1073
1083
|
"sample_skill:executor",
|
|
@@ -1375,9 +1385,11 @@ class CliTests(unittest.TestCase):
|
|
|
1375
1385
|
str(out),
|
|
1376
1386
|
"--item",
|
|
1377
1387
|
"WI-001",
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1388
|
+
"--text",
|
|
1389
|
+
"Implement controlled output",
|
|
1390
|
+
"--completion-criterion",
|
|
1391
|
+
"output is written and validated",
|
|
1392
|
+
"--status",
|
|
1381
1393
|
"done",
|
|
1382
1394
|
"--role",
|
|
1383
1395
|
"sample_skill:executor",
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import io
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from xrefkit.__main__ import main
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _run(root: Path, *args: str) -> int:
|
|
10
|
+
with contextlib.redirect_stdout(io.StringIO()):
|
|
11
|
+
argv = list(args)
|
|
12
|
+
if argv[:2] == ["workflow", "run"]:
|
|
13
|
+
argv.extend(["--root", str(root)])
|
|
14
|
+
return main(argv)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_instruction_workflow_requires_completion_conditions(tmp_path: Path) -> None:
|
|
18
|
+
out = tmp_path / "work" / "sessions" / "run.md"
|
|
19
|
+
assert _run(tmp_path, "workflow", "run", "--task", "Do work", "--out", str(out)) == 1
|
|
20
|
+
assert not out.exists()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_instruction_workflow_uses_default_conditions_and_shared_protocol(tmp_path: Path) -> None:
|
|
24
|
+
out = tmp_path / "work" / "sessions" / "run.md"
|
|
25
|
+
assert _run(
|
|
26
|
+
tmp_path,
|
|
27
|
+
"workflow",
|
|
28
|
+
"run",
|
|
29
|
+
"--task",
|
|
30
|
+
"Do work",
|
|
31
|
+
"--out",
|
|
32
|
+
str(out),
|
|
33
|
+
"--use-default-completion-conditions",
|
|
34
|
+
) == 0
|
|
35
|
+
text = out.read_text(encoding="utf-8")
|
|
36
|
+
assert "# Workflow Run Log" in text
|
|
37
|
+
assert "## Run Load Gate" in text
|
|
38
|
+
assert "- basis: `default`" in text
|
|
39
|
+
assert "- quality_policy: `human_acceptance`" in text
|
|
40
|
+
|
|
41
|
+
assert _run(
|
|
42
|
+
tmp_path,
|
|
43
|
+
"skill",
|
|
44
|
+
"workitem",
|
|
45
|
+
"--log",
|
|
46
|
+
str(out),
|
|
47
|
+
"--item",
|
|
48
|
+
"WI-001",
|
|
49
|
+
"--text",
|
|
50
|
+
"Perform the instruction",
|
|
51
|
+
"--completion-criterion",
|
|
52
|
+
"instruction result is recorded and verified",
|
|
53
|
+
"--status",
|
|
54
|
+
"done",
|
|
55
|
+
"--role",
|
|
56
|
+
"instruction:executor",
|
|
57
|
+
) == 0
|
|
58
|
+
for artifact_id, kind, target, role in (
|
|
59
|
+
("OUT-001", "output", "output.md", "instruction:executor"),
|
|
60
|
+
("EVD-001", "evidence", "test command", "instruction:checker"),
|
|
61
|
+
):
|
|
62
|
+
assert _run(
|
|
63
|
+
tmp_path,
|
|
64
|
+
"skill",
|
|
65
|
+
"artifact",
|
|
66
|
+
"--log",
|
|
67
|
+
str(out),
|
|
68
|
+
"--artifact",
|
|
69
|
+
artifact_id,
|
|
70
|
+
"--kind",
|
|
71
|
+
kind,
|
|
72
|
+
"--target",
|
|
73
|
+
target,
|
|
74
|
+
"--item",
|
|
75
|
+
"WI-001",
|
|
76
|
+
"--status",
|
|
77
|
+
"done",
|
|
78
|
+
"--role",
|
|
79
|
+
role,
|
|
80
|
+
) == 0
|
|
81
|
+
assert _run(tmp_path, "skill", "phase", "--log", str(out), "--phase", "execution", "--status", "done", "--role", "instruction:executor") == 0
|
|
82
|
+
assert _run(tmp_path, "skill", "phase", "--log", str(out), "--phase", "handoff", "--status", "done", "--role", "instruction:handoff_owner") == 0
|
|
83
|
+
assert _run(tmp_path, "skill", "verify", "--log", str(out)) == 0
|
|
84
|
+
|
|
85
|
+
# Quality is a human decision and is recorded separately from progression.
|
|
86
|
+
assert _run(
|
|
87
|
+
tmp_path,
|
|
88
|
+
"skill",
|
|
89
|
+
"feedback",
|
|
90
|
+
"--log",
|
|
91
|
+
str(out),
|
|
92
|
+
"--kind",
|
|
93
|
+
"human",
|
|
94
|
+
"--status",
|
|
95
|
+
"accepted",
|
|
96
|
+
"--target",
|
|
97
|
+
"OUT-001",
|
|
98
|
+
"--note",
|
|
99
|
+
"human accepted output quality",
|
|
100
|
+
) == 0
|
|
101
|
+
assert _run(tmp_path, "skill", "close", "--log", str(out)) == 0
|
|
102
|
+
assert "## Completion Conditions" in out.read_text(encoding="utf-8")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_workitem_requires_criterion_or_explicit_unknown_reason(tmp_path: Path) -> None:
|
|
106
|
+
out = tmp_path / "work" / "sessions" / "run.md"
|
|
107
|
+
assert _run(
|
|
108
|
+
tmp_path,
|
|
109
|
+
"workflow",
|
|
110
|
+
"run",
|
|
111
|
+
"--task",
|
|
112
|
+
"Do work",
|
|
113
|
+
"--out",
|
|
114
|
+
str(out),
|
|
115
|
+
"--use-default-completion-conditions",
|
|
116
|
+
) == 0
|
|
117
|
+
assert _run(
|
|
118
|
+
tmp_path,
|
|
119
|
+
"skill",
|
|
120
|
+
"workitem",
|
|
121
|
+
"--log",
|
|
122
|
+
str(out),
|
|
123
|
+
"--item",
|
|
124
|
+
"WI-001",
|
|
125
|
+
"--text",
|
|
126
|
+
"Investigate missing requirement",
|
|
127
|
+
"--status",
|
|
128
|
+
"pending",
|
|
129
|
+
"--role",
|
|
130
|
+
"instruction:executor",
|
|
131
|
+
) == 1
|
|
132
|
+
assert _run(
|
|
133
|
+
tmp_path,
|
|
134
|
+
"skill",
|
|
135
|
+
"workitem",
|
|
136
|
+
"--log",
|
|
137
|
+
str(out),
|
|
138
|
+
"--item",
|
|
139
|
+
"WI-001",
|
|
140
|
+
"--text",
|
|
141
|
+
"Investigate missing requirement",
|
|
142
|
+
"--status",
|
|
143
|
+
"unknown",
|
|
144
|
+
"--criterion-unknown-reason",
|
|
145
|
+
"The business owner has not defined the acceptance outcome",
|
|
146
|
+
"--role",
|
|
147
|
+
"instruction:executor",
|
|
148
|
+
) == 0
|
|
149
|
+
text = out.read_text(encoding="utf-8")
|
|
150
|
+
assert "criterion=`` reason=`The business owner has not defined the acceptance outcome`" in text
|
|
151
|
+
assert _run(tmp_path, "skill", "phase", "--log", str(out), "--phase", "execution", "--status", "done", "--role", "instruction:executor") == 0
|
|
152
|
+
assert _run(tmp_path, "skill", "verify", "--log", str(out)) == 1
|
|
@@ -84,6 +84,8 @@ class SkillRuntimeAuditTests(unittest.TestCase):
|
|
|
84
84
|
"WI-001",
|
|
85
85
|
"--text",
|
|
86
86
|
"Implement controlled output",
|
|
87
|
+
"--completion-criterion",
|
|
88
|
+
"output is written and validated",
|
|
87
89
|
"--status",
|
|
88
90
|
"done",
|
|
89
91
|
"--role",
|
|
@@ -322,6 +324,7 @@ class SkillRuntimeAuditTests(unittest.TestCase):
|
|
|
322
324
|
[
|
|
323
325
|
"skill", "workitem", "--log", str(out), "--item", "WI-001",
|
|
324
326
|
"--text", "Implement controlled output", "--status", "done",
|
|
327
|
+
"--completion-criterion", "output is written and validated",
|
|
325
328
|
"--role", "sample_skill:executor",
|
|
326
329
|
]
|
|
327
330
|
),
|
|
@@ -393,7 +396,7 @@ class SkillRuntimeAuditTests(unittest.TestCase):
|
|
|
393
396
|
main(
|
|
394
397
|
[
|
|
395
398
|
"skill", "workitem", "--log", str(out), "--item", "WI-001",
|
|
396
|
-
"--text", "do work", "--status", "done", "--role", "sample_skill:executor",
|
|
399
|
+
"--text", "do work", "--status", "done", "--completion-criterion", "work is verified", "--role", "sample_skill:executor",
|
|
397
400
|
]
|
|
398
401
|
),
|
|
399
402
|
)
|
|
@@ -6,7 +6,7 @@ import sys
|
|
|
6
6
|
from collections.abc import Sequence
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
_OPERATIONS = {"xref", "ctx", "goal", "gate", "pack", "dashboard", "analysis", "skill"}
|
|
9
|
+
_OPERATIONS = {"xref", "ctx", "goal", "gate", "pack", "dashboard", "analysis", "skill", "workflow"}
|
|
10
10
|
_V2 = {"package", "show"}
|
|
11
11
|
|
|
12
12
|
|
|
@@ -18,6 +18,7 @@ def _print_help() -> None:
|
|
|
18
18
|
" xref manage XIDs and references\n"
|
|
19
19
|
" ctx build compact context packs\n"
|
|
20
20
|
" skill discover, validate, run, verify, and close Skills\n"
|
|
21
|
+
" workflow run the generic protocol for instructions without a Skill\n"
|
|
21
22
|
" tools list and run XID-backed client tools\n"
|
|
22
23
|
" catalog list and maintain Knowledge and structure catalogs\n"
|
|
23
24
|
" pack validate and build runtime/content packs\n"
|
|
@@ -497,6 +497,8 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
497
497
|
p_skill_workitem.add_argument("--log", required=True, help="Skill run log to update")
|
|
498
498
|
p_skill_workitem.add_argument("--item", required=True, help="Stable work item id, such as WI-001")
|
|
499
499
|
p_skill_workitem.add_argument("--text", default=None, help="Work item text; required when adding a new item")
|
|
500
|
+
p_skill_workitem.add_argument("--completion-criterion", default=None, help="Observable procedural condition for this work item")
|
|
501
|
+
p_skill_workitem.add_argument("--criterion-unknown-reason", default=None, help="Why the completion criterion cannot yet be defined for unknown/blocked/escalated work")
|
|
500
502
|
p_skill_workitem.add_argument(
|
|
501
503
|
"--status",
|
|
502
504
|
required=True,
|
|
@@ -574,6 +576,33 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
574
576
|
p_skill_verify.add_argument("--note", default=None, help="Optional check event note")
|
|
575
577
|
p_skill_verify.add_argument("--json", action="store_true", help="Emit JSON")
|
|
576
578
|
|
|
579
|
+
workflow = subparsers.add_parser(
|
|
580
|
+
"workflow",
|
|
581
|
+
help="Run the generic workflow protocol for an instruction without a Skill",
|
|
582
|
+
)
|
|
583
|
+
workflow_sub = workflow.add_subparsers(dest="workflow_cmd", required=True)
|
|
584
|
+
p_workflow_run = workflow_sub.add_parser(
|
|
585
|
+
"run",
|
|
586
|
+
help="Open an instruction-backed workflow run with explicit or default completion conditions",
|
|
587
|
+
)
|
|
588
|
+
p_workflow_run.add_argument("--root", default=".", help="Project root (default: .)")
|
|
589
|
+
p_workflow_run.add_argument("--task", default=None, help="Instruction text")
|
|
590
|
+
p_workflow_run.add_argument("--task-file", default=None, help="Read instruction text from a UTF-8 file")
|
|
591
|
+
p_workflow_run.add_argument("--out", default=None, help="Write workflow run log to this path")
|
|
592
|
+
p_workflow_run.add_argument("--run-id", default=None, help="Caller-supplied UUID")
|
|
593
|
+
p_workflow_run.add_argument(
|
|
594
|
+
"--completion-condition",
|
|
595
|
+
action="append",
|
|
596
|
+
default=[],
|
|
597
|
+
help="Explicit procedural completion condition; may be repeated",
|
|
598
|
+
)
|
|
599
|
+
p_workflow_run.add_argument(
|
|
600
|
+
"--use-default-completion-conditions",
|
|
601
|
+
action="store_true",
|
|
602
|
+
help="Use the repository's procedural completion conditions when the instruction omits them",
|
|
603
|
+
)
|
|
604
|
+
p_workflow_run.add_argument("--json", action="store_true", help="Emit JSON")
|
|
605
|
+
|
|
577
606
|
return parser
|
|
578
607
|
|
|
579
608
|
|
|
@@ -675,6 +704,12 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
675
704
|
|
|
676
705
|
return cmd_skill(args)
|
|
677
706
|
|
|
707
|
+
if args.command == "workflow":
|
|
708
|
+
if args.workflow_cmd == "run":
|
|
709
|
+
from xrefkit.skillrun import cmd_workflow_run
|
|
710
|
+
|
|
711
|
+
return cmd_workflow_run(args)
|
|
712
|
+
|
|
678
713
|
if args.command == "pack":
|
|
679
714
|
if args.pack_cmd == "list":
|
|
680
715
|
from xrefkit.packmeta import cmd_pack_list
|
|
@@ -179,6 +179,11 @@ WORKLIST_ROWS = [
|
|
|
179
179
|
("Handoff", "Record outputs, unresolved items, next owner, and human decision points."),
|
|
180
180
|
]
|
|
181
181
|
WORKITEM_RE = re.compile(
|
|
182
|
+
r"^- \[(?P<checkbox>[ x!])\] (?P<item_id>[A-Za-z0-9_.-]+) "
|
|
183
|
+
r"status=`(?P<status>[^`]+)` role=`(?P<role>[^`]+)` "
|
|
184
|
+
r"criterion=`(?P<criterion>[^`]*)` reason=`(?P<reason>[^`]*)`: (?P<text>.*)$"
|
|
185
|
+
)
|
|
186
|
+
LEGACY_WORKITEM_RE = re.compile(
|
|
182
187
|
r"^- \[(?P<checkbox>[ x!])\] (?P<item_id>[A-Za-z0-9_.-]+) "
|
|
183
188
|
r"status=`(?P<status>[^`]+)` role=`(?P<role>[^`]+)`: (?P<text>.*)$"
|
|
184
189
|
)
|
|
@@ -748,6 +753,18 @@ def _log_skill_id(text: str) -> str | None:
|
|
|
748
753
|
return text[start:end]
|
|
749
754
|
|
|
750
755
|
|
|
756
|
+
def _has_opened_run_gate(text: str) -> bool:
|
|
757
|
+
"""Return whether this log was opened by a supported workflow runner.
|
|
758
|
+
|
|
759
|
+
Skill runs retain their historical gate text. Instruction-backed workflow
|
|
760
|
+
runs use the generic gate text, but share the same progression machinery.
|
|
761
|
+
"""
|
|
762
|
+
return (
|
|
763
|
+
"## Skill Load Gate\n\n- status: `opened_by_xrefkit_skill_run`" in text
|
|
764
|
+
or "## Run Load Gate\n\n- status: `opened_by_xrefkit_workflow_run`" in text
|
|
765
|
+
)
|
|
766
|
+
|
|
767
|
+
|
|
751
768
|
def _log_model_tier(text: str) -> str | None:
|
|
752
769
|
prefix = "- model_tier: `"
|
|
753
770
|
start = text.find(prefix)
|
|
@@ -813,6 +830,19 @@ def _parse_work_items(text: str) -> list[dict[str, str]]:
|
|
|
813
830
|
items: list[dict[str, str]] = []
|
|
814
831
|
for line in body.splitlines():
|
|
815
832
|
match = WORKITEM_RE.match(line)
|
|
833
|
+
if match:
|
|
834
|
+
items.append(
|
|
835
|
+
{
|
|
836
|
+
"item_id": match.group("item_id"),
|
|
837
|
+
"status": match.group("status"),
|
|
838
|
+
"role": match.group("role"),
|
|
839
|
+
"criterion": match.group("criterion"),
|
|
840
|
+
"reason": match.group("reason"),
|
|
841
|
+
"text": match.group("text"),
|
|
842
|
+
}
|
|
843
|
+
)
|
|
844
|
+
continue
|
|
845
|
+
match = LEGACY_WORKITEM_RE.match(line)
|
|
816
846
|
if not match:
|
|
817
847
|
continue
|
|
818
848
|
items.append(
|
|
@@ -820,14 +850,19 @@ def _parse_work_items(text: str) -> list[dict[str, str]]:
|
|
|
820
850
|
"item_id": match.group("item_id"),
|
|
821
851
|
"status": match.group("status"),
|
|
822
852
|
"role": match.group("role"),
|
|
853
|
+
"criterion": "",
|
|
854
|
+
"reason": "legacy work item has no recorded completion criterion",
|
|
823
855
|
"text": match.group("text"),
|
|
824
856
|
}
|
|
825
857
|
)
|
|
826
858
|
return items
|
|
827
859
|
|
|
828
860
|
|
|
829
|
-
def _render_workitem_line(*, item_id: str, status: str, role: str, text: str) -> str:
|
|
830
|
-
return
|
|
861
|
+
def _render_workitem_line(*, item_id: str, status: str, role: str, criterion: str, reason: str, text: str) -> str:
|
|
862
|
+
return (
|
|
863
|
+
f"- [{_workitem_checkbox(status)}] {item_id} status=`{status}` role=`{role}` "
|
|
864
|
+
f"criterion=`{criterion}` reason=`{reason}`: {text}"
|
|
865
|
+
)
|
|
831
866
|
|
|
832
867
|
|
|
833
868
|
def _overall_workitem_status(items: list[dict[str, str]]) -> str:
|
|
@@ -848,7 +883,7 @@ def _replace_concrete_work_items_section(text: str, items: list[dict[str, str]])
|
|
|
848
883
|
insert_at = text.find("\n## Execution Role")
|
|
849
884
|
if insert_at == -1:
|
|
850
885
|
insert_at = len(text)
|
|
851
|
-
section = "\n\n## Concrete Work Items\n\n- status: `pending`\n- rule:
|
|
886
|
+
section = "\n\n## Concrete Work Items\n\n- status: `pending`\n- rule: each work item requires a completion criterion; use unknown, blocked, or escalated with a reason when the criterion cannot yet be defined\n"
|
|
852
887
|
text = text[:insert_at] + section + text[insert_at:]
|
|
853
888
|
body, start, end = _section_body(text, "Concrete Work Items")
|
|
854
889
|
if body is None:
|
|
@@ -859,7 +894,7 @@ def _replace_concrete_work_items_section(text: str, items: list[dict[str, str]])
|
|
|
859
894
|
"## Concrete Work Items",
|
|
860
895
|
"",
|
|
861
896
|
f"- status: `{status}`",
|
|
862
|
-
"- rule:
|
|
897
|
+
"- rule: each work item requires a completion criterion; use unknown, blocked, or escalated with a reason when the criterion cannot yet be defined",
|
|
863
898
|
]
|
|
864
899
|
lines.extend(_render_workitem_line(**item) for item in items)
|
|
865
900
|
new_body = "\n".join(lines) + "\n"
|
|
@@ -876,15 +911,21 @@ def update_work_item(args) -> SkillRunResult:
|
|
|
876
911
|
status = str(args.status).lower()
|
|
877
912
|
role = str(args.role).strip()
|
|
878
913
|
item_text = str(args.text or "").strip()
|
|
914
|
+
criterion = str(getattr(args, "completion_criterion", None) or "").strip().replace("`", "'").replace("\n", " ")
|
|
915
|
+
reason = str(getattr(args, "criterion_unknown_reason", None) or "").strip().replace("`", "'").replace("\n", " ")
|
|
879
916
|
if not item_id:
|
|
880
917
|
return SkillRunResult(ok=False, skill_id=None, skill_doc=None, run_log=str(log_path), errors=["missing --item"])
|
|
881
918
|
if status not in VALID_WORKITEM_STATUSES:
|
|
882
919
|
return SkillRunResult(ok=False, skill_id=None, skill_doc=None, run_log=str(log_path), errors=[f"invalid work item status: {status}"])
|
|
883
920
|
if not role:
|
|
884
921
|
return SkillRunResult(ok=False, skill_id=None, skill_doc=None, run_log=str(log_path), errors=["missing --role"])
|
|
922
|
+
if not criterion and not reason and status in {"unknown", "blocked", "escalated"}:
|
|
923
|
+
return SkillRunResult(ok=False, skill_id=None, skill_doc=None, run_log=str(log_path), errors=["completion criterion is undefined; provide --criterion-unknown-reason for unknown, blocked, or escalated work items"])
|
|
924
|
+
if not criterion and status in {"pending", "in_progress", "done"}:
|
|
925
|
+
return SkillRunResult(ok=False, skill_id=None, skill_doc=None, run_log=str(log_path), errors=["--completion-criterion is required for pending, in_progress, and done work items"])
|
|
885
926
|
|
|
886
927
|
text = log_path.read_text(encoding="utf-8")
|
|
887
|
-
if
|
|
928
|
+
if not _has_opened_run_gate(text):
|
|
888
929
|
return SkillRunResult(ok=False, skill_id=None, skill_doc=None, run_log=str(log_path), errors=["skill run log is missing an opened Skill Load Gate"])
|
|
889
930
|
|
|
890
931
|
items = _parse_work_items(text)
|
|
@@ -892,12 +933,16 @@ def update_work_item(args) -> SkillRunResult:
|
|
|
892
933
|
if existing:
|
|
893
934
|
existing["status"] = status
|
|
894
935
|
existing["role"] = role
|
|
936
|
+
if criterion:
|
|
937
|
+
existing["criterion"] = criterion
|
|
938
|
+
if reason:
|
|
939
|
+
existing["reason"] = reason
|
|
895
940
|
if item_text:
|
|
896
941
|
existing["text"] = item_text
|
|
897
942
|
else:
|
|
898
943
|
if not item_text:
|
|
899
944
|
return SkillRunResult(ok=False, skill_id=None, skill_doc=None, run_log=str(log_path), errors=["new work item requires --text"])
|
|
900
|
-
items.append({"item_id": item_id, "status": status, "role": role, "text": item_text})
|
|
945
|
+
items.append({"item_id": item_id, "status": status, "role": role, "criterion": criterion, "reason": reason, "text": item_text})
|
|
901
946
|
|
|
902
947
|
text = _replace_concrete_work_items_section(text, items)
|
|
903
948
|
text = _append_phase_event(text, phase=f"workitem:{item_id}", status=status, role=role, note=item_text or None)
|
|
@@ -1004,7 +1049,7 @@ def update_artifact(args) -> SkillRunResult:
|
|
|
1004
1049
|
return SkillRunResult(ok=False, skill_id=None, skill_doc=None, run_log=str(log_path), errors=["missing --role"])
|
|
1005
1050
|
|
|
1006
1051
|
text = log_path.read_text(encoding="utf-8")
|
|
1007
|
-
if
|
|
1052
|
+
if not _has_opened_run_gate(text):
|
|
1008
1053
|
return SkillRunResult(ok=False, skill_id=None, skill_doc=None, run_log=str(log_path), errors=["skill run log is missing an opened Skill Load Gate"])
|
|
1009
1054
|
|
|
1010
1055
|
artifacts = _parse_artifacts(text)
|
|
@@ -1140,7 +1185,7 @@ def update_concern(args) -> SkillRunResult:
|
|
|
1140
1185
|
return SkillRunResult(ok=False, skill_id=None, skill_doc=None, run_log=str(log_path), errors=["missing --role"])
|
|
1141
1186
|
|
|
1142
1187
|
text = log_path.read_text(encoding="utf-8")
|
|
1143
|
-
if
|
|
1188
|
+
if not _has_opened_run_gate(text):
|
|
1144
1189
|
return SkillRunResult(ok=False, skill_id=None, skill_doc=None, run_log=str(log_path), errors=["skill run log is missing an opened Skill Load Gate"])
|
|
1145
1190
|
|
|
1146
1191
|
concerns = _parse_concerns(text)
|
|
@@ -1388,7 +1433,7 @@ def _validate_handoff_sources(root: Path, source_logs: list[str]) -> tuple[list[
|
|
|
1388
1433
|
errors.append(f"handoff source log not found: {source_path}")
|
|
1389
1434
|
continue
|
|
1390
1435
|
text = source_path.read_text(encoding="utf-8")
|
|
1391
|
-
if
|
|
1436
|
+
if not _has_opened_run_gate(text):
|
|
1392
1437
|
errors.append(f"handoff source log was not opened by xrefkit skill run: {source_path}")
|
|
1393
1438
|
continue
|
|
1394
1439
|
closure_status = _section_status(text, "Closure Gate")
|
|
@@ -1424,6 +1469,13 @@ def _progression_record_errors(
|
|
|
1424
1469
|
if not work_items:
|
|
1425
1470
|
errors.append("at least one concrete work item is required before closure")
|
|
1426
1471
|
for item in work_items:
|
|
1472
|
+
if not item.get("criterion"):
|
|
1473
|
+
if item["status"] in {"unknown", "blocked", "escalated"} and item.get("reason"):
|
|
1474
|
+
pass
|
|
1475
|
+
else:
|
|
1476
|
+
errors.append(f"work item {item['item_id']} must record a completion criterion or a reason why it cannot be defined")
|
|
1477
|
+
if item.get("criterion") == "unknown" and item["status"] in {"pending", "in_progress", "done"}:
|
|
1478
|
+
errors.append(f"work item {item['item_id']} cannot use unknown as its completion criterion while executable")
|
|
1427
1479
|
if item["status"] not in ACCEPTED_CLOSE_STATUSES:
|
|
1428
1480
|
errors.append(
|
|
1429
1481
|
f"work item {item['item_id']} must be done or escalated before closure; current={item['status']}"
|
|
@@ -1463,7 +1515,7 @@ def verify_progression_run(args) -> SkillRunResult:
|
|
|
1463
1515
|
return SkillRunResult(ok=False, skill_id=None, skill_doc=None, run_log=None, errors=[f"log not found: {log_path}"])
|
|
1464
1516
|
|
|
1465
1517
|
text = log_path.read_text(encoding="utf-8")
|
|
1466
|
-
if
|
|
1518
|
+
if not _has_opened_run_gate(text):
|
|
1467
1519
|
return SkillRunResult(
|
|
1468
1520
|
ok=False,
|
|
1469
1521
|
skill_id=None,
|
|
@@ -1512,7 +1564,7 @@ def close_skill_run(args) -> SkillRunResult:
|
|
|
1512
1564
|
return SkillRunResult(ok=False, skill_id=None, skill_doc=None, run_log=None, errors=[f"log not found: {log_path}"])
|
|
1513
1565
|
|
|
1514
1566
|
text = log_path.read_text(encoding="utf-8")
|
|
1515
|
-
if
|
|
1567
|
+
if not _has_opened_run_gate(text):
|
|
1516
1568
|
return SkillRunResult(
|
|
1517
1569
|
ok=False,
|
|
1518
1570
|
skill_id=None,
|
|
@@ -1663,7 +1715,7 @@ def _validate_observation_log(log_path: Path) -> tuple[str | None, SkillRunResul
|
|
|
1663
1715
|
run_log=str(log_path),
|
|
1664
1716
|
errors=[f"could not read skill run log: {exc}"],
|
|
1665
1717
|
)
|
|
1666
|
-
if
|
|
1718
|
+
if not _has_opened_run_gate(text):
|
|
1667
1719
|
return None, SkillRunResult(
|
|
1668
1720
|
ok=False,
|
|
1669
1721
|
skill_id=None,
|
|
@@ -2003,7 +2055,7 @@ def update_token_usage(args) -> SkillRunResult:
|
|
|
2003
2055
|
)
|
|
2004
2056
|
|
|
2005
2057
|
text = log_path.read_text(encoding="utf-8")
|
|
2006
|
-
if
|
|
2058
|
+
if not _has_opened_run_gate(text):
|
|
2007
2059
|
return SkillRunResult(ok=False, skill_id=None, skill_doc=None, run_log=str(log_path), errors=["skill run log is missing an opened Skill Load Gate"])
|
|
2008
2060
|
|
|
2009
2061
|
total_tokens = total_arg if total_arg is not None else (input_tokens or 0) + (output_tokens or 0)
|
|
@@ -2225,6 +2277,126 @@ def run_skill(args) -> SkillRunResult:
|
|
|
2225
2277
|
)
|
|
2226
2278
|
|
|
2227
2279
|
|
|
2280
|
+
DEFAULT_INSTRUCTION_COMPLETION_CONDITIONS = (
|
|
2281
|
+
"all concrete work items are done or escalated",
|
|
2282
|
+
"an output artifact and an evidence artifact are recorded",
|
|
2283
|
+
"unknowns and risks are resolved or escalated",
|
|
2284
|
+
"execution, check, and handoff phases are complete or escalated",
|
|
2285
|
+
)
|
|
2286
|
+
|
|
2287
|
+
|
|
2288
|
+
def run_workflow_instruction(args) -> SkillRunResult:
|
|
2289
|
+
"""Open a generic workflow run for an instruction without a Skill.
|
|
2290
|
+
|
|
2291
|
+
This intentionally creates the same run-log shape consumed by the
|
|
2292
|
+
progression commands. It does not infer business quality; that remains a
|
|
2293
|
+
human acceptance decision recorded separately with ``skill feedback``.
|
|
2294
|
+
"""
|
|
2295
|
+
root = Path(args.root).resolve()
|
|
2296
|
+
task, task_errors = _read_task(args)
|
|
2297
|
+
if task_errors:
|
|
2298
|
+
return SkillRunResult(ok=False, skill_id=None, skill_doc=None, run_log=None, errors=task_errors)
|
|
2299
|
+
|
|
2300
|
+
explicit = [str(value).strip() for value in getattr(args, "completion_condition", []) if str(value).strip()]
|
|
2301
|
+
use_default = bool(getattr(args, "use_default_completion_conditions", False))
|
|
2302
|
+
if not explicit and not use_default:
|
|
2303
|
+
return SkillRunResult(
|
|
2304
|
+
ok=False,
|
|
2305
|
+
skill_id=None,
|
|
2306
|
+
skill_doc=None,
|
|
2307
|
+
run_log=None,
|
|
2308
|
+
errors=[
|
|
2309
|
+
"completion conditions are required; provide --completion-condition or explicitly opt into --use-default-completion-conditions"
|
|
2310
|
+
],
|
|
2311
|
+
)
|
|
2312
|
+
conditions = explicit or list(DEFAULT_INSTRUCTION_COMPLETION_CONDITIONS)
|
|
2313
|
+
basis = "explicit" if explicit else "default"
|
|
2314
|
+
|
|
2315
|
+
raw_run_id = str(getattr(args, "run_id", None) or uuid.uuid4())
|
|
2316
|
+
try:
|
|
2317
|
+
run_id = str(uuid.UUID(raw_run_id))
|
|
2318
|
+
except ValueError:
|
|
2319
|
+
return SkillRunResult(ok=False, skill_id=None, skill_doc=None, run_log=None, errors=[f"run_id must be a UUID: {raw_run_id}"])
|
|
2320
|
+
|
|
2321
|
+
out_path = Path(args.out) if args.out else _default_log_path(root, "instruction")
|
|
2322
|
+
if not out_path.is_absolute():
|
|
2323
|
+
out_path = root / out_path
|
|
2324
|
+
sessions_dir = root / "work" / "sessions"
|
|
2325
|
+
if sessions_dir.exists():
|
|
2326
|
+
for existing_log in sessions_dir.rglob("*.md"):
|
|
2327
|
+
try:
|
|
2328
|
+
existing_text = existing_log.read_text(encoding="utf-8")
|
|
2329
|
+
except (OSError, UnicodeError):
|
|
2330
|
+
continue
|
|
2331
|
+
if _log_field(existing_text, "run_id") == run_id:
|
|
2332
|
+
return SkillRunResult(ok=False, skill_id=None, skill_doc=None, run_log=None, errors=[f"run_id is already used by an existing workflow run: {existing_log}"])
|
|
2333
|
+
|
|
2334
|
+
assigned_roles = _assign_runtime_roles(skill_id="instruction", execution_mode="local_default", model_tier=None)
|
|
2335
|
+
log = _render_log(
|
|
2336
|
+
run_id=run_id,
|
|
2337
|
+
skill_id="instruction",
|
|
2338
|
+
maturity="not_applicable",
|
|
2339
|
+
meta_path=Path("-") ,
|
|
2340
|
+
skill_doc=Path("-"),
|
|
2341
|
+
execution_mode="local_default",
|
|
2342
|
+
guard_policy="required",
|
|
2343
|
+
capability_layering="not_applicable",
|
|
2344
|
+
workflow_protocol="required",
|
|
2345
|
+
capability="instruction execution",
|
|
2346
|
+
tuning="generic procedural completion",
|
|
2347
|
+
role_responsibilities={"executor": "execute the user instruction"},
|
|
2348
|
+
capability_refs=[],
|
|
2349
|
+
assigned_roles=assigned_roles,
|
|
2350
|
+
task=str(task),
|
|
2351
|
+
os_contract=dict(REQUIRED_OS_CONTRACT),
|
|
2352
|
+
handoff_sources=[],
|
|
2353
|
+
model_tier=None,
|
|
2354
|
+
domain_knowledge={"available": [], "selected": {}, "requirements": []},
|
|
2355
|
+
)
|
|
2356
|
+
log = log.replace("# Skill Run Log", "# Workflow Run Log", 1)
|
|
2357
|
+
log = log.replace("## Skill Load Gate", "## Run Load Gate", 1)
|
|
2358
|
+
log = log.replace("opened_by_xrefkit_skill_run", "opened_by_xrefkit_workflow_run", 1)
|
|
2359
|
+
log = log.replace("- rule: do not open or execute the Skill procedure until this runtime envelope exists", "- rule: do not treat the instruction as procedurally complete until this workflow envelope closes", 1)
|
|
2360
|
+
log = log.replace("## Skill Routing Trace", "## Instruction Routing Trace", 1)
|
|
2361
|
+
condition_lines = [
|
|
2362
|
+
"## Completion Conditions",
|
|
2363
|
+
"",
|
|
2364
|
+
f"- basis: `{basis}`",
|
|
2365
|
+
"- quality_policy: `human_acceptance`",
|
|
2366
|
+
"- rule: workflow verification checks procedural records; a human confirms output quality separately",
|
|
2367
|
+
] + [f"- condition: {condition.replace('`', "'")}" for condition in conditions]
|
|
2368
|
+
marker = "\n## Startup Inputs\n"
|
|
2369
|
+
log = log.replace(marker, "\n" + "\n".join(condition_lines) + "\n" + marker, 1)
|
|
2370
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
2371
|
+
with _LogFileLock(out_path.with_name(f".{out_path.name}.lock")):
|
|
2372
|
+
_atomic_write_text(out_path, log)
|
|
2373
|
+
return SkillRunResult(
|
|
2374
|
+
ok=True,
|
|
2375
|
+
skill_id="instruction",
|
|
2376
|
+
skill_doc=None,
|
|
2377
|
+
run_log=str(out_path),
|
|
2378
|
+
errors=[],
|
|
2379
|
+
assigned_roles=assigned_roles,
|
|
2380
|
+
run_id=run_id,
|
|
2381
|
+
)
|
|
2382
|
+
|
|
2383
|
+
|
|
2384
|
+
def cmd_workflow_run(args) -> int:
|
|
2385
|
+
result = run_workflow_instruction(args)
|
|
2386
|
+
if args.json:
|
|
2387
|
+
print(json.dumps(result.to_dict(), ensure_ascii=False, indent=2))
|
|
2388
|
+
elif result.ok:
|
|
2389
|
+
print(f"ok: {result.run_log}")
|
|
2390
|
+
print(f" run_id: {result.run_id}")
|
|
2391
|
+
print(" run_type: instruction")
|
|
2392
|
+
print(" next: add work items, record artifacts/evidence, verify, human-accept output, then close")
|
|
2393
|
+
else:
|
|
2394
|
+
print("fail: workflow run")
|
|
2395
|
+
for error in result.errors:
|
|
2396
|
+
print(f" error: {error}")
|
|
2397
|
+
return 0 if result.ok else 1
|
|
2398
|
+
|
|
2399
|
+
|
|
2228
2400
|
def cmd_skill_run(args) -> int:
|
|
2229
2401
|
result = run_skill(args)
|
|
2230
2402
|
if args.json:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xrefkit
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Portable XID, Skill, Knowledge, workflow, and MCP runtime for XRefKit
|
|
5
5
|
Author: synthaicode
|
|
6
6
|
License: MIT License
|
|
@@ -91,7 +91,7 @@ XRefKit makes AI work explicit by separating:
|
|
|
91
91
|
|
|
92
92
|
- Skills: executable work units, each identified by a capability/tuning/responsibility triad and carrying its execution and check contract
|
|
93
93
|
- Knowledge: source-backed domain facts and local rules loaded only when needed
|
|
94
|
-
- Workflow protocol: the generic, deterministic
|
|
94
|
+
- Workflow protocol: the generic, deterministic control for Skill-backed and instruction-backed runs (phases, verification, closure)
|
|
95
95
|
- Semantic routing: selecting the right Skill for a goal from user intent and the Skill catalog
|
|
96
96
|
- Evidence: logs, judgments, concerns, and quality checks
|
|
97
97
|
- XIDs: stable references that survive file movement and restructuring so AI can load targeted context without treating the whole repository as one prompt
|
|
@@ -109,6 +109,21 @@ and handoff records from collapsing into one opaque instruction block.
|
|
|
109
109
|
4. Agents are routed semantically to the right Skill and load only the relevant context.
|
|
110
110
|
5. Evidence and quality gates make incomplete or unsupported work visible.
|
|
111
111
|
|
|
112
|
+
When an instruction has no matching Skill, open an instruction-backed workflow
|
|
113
|
+
run explicitly. The run requires either user-supplied procedural completion
|
|
114
|
+
conditions or an explicit opt-in to the repository defaults:
|
|
115
|
+
|
|
116
|
+
```powershell
|
|
117
|
+
xrefkit workflow run --task "Perform the requested procedure" `
|
|
118
|
+
--use-default-completion-conditions --json
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`verify` and `close` determine procedural completion only. Output quality is
|
|
122
|
+
recorded separately after human acceptance with the existing feedback record.
|
|
123
|
+
Each work item also requires its own completion criterion; if that criterion is
|
|
124
|
+
not yet definable, record the item as unknown, blocked, or escalated with a
|
|
125
|
+
reason instead of inventing a criterion.
|
|
126
|
+
|
|
112
127
|
## Quick Start
|
|
113
128
|
|
|
114
129
|
Install the package and initialize an instance:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/resources/base/generations/7a682a5272907354/contracts.json
RENAMED
|
File without changes
|
{xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/resources/base/generations/7a682a5272907354/model_body.md
RENAMED
|
File without changes
|
{xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/resources/base/generations/9929294385ccb7b0/contracts.json
RENAMED
|
File without changes
|
{xrefkit-0.4.0 → xrefkit-0.4.2}/xrefkit/resources/base/generations/9929294385ccb7b0/model_body.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|