hermes-task-framework 1.2.0__tar.gz → 1.3.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.
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/PKG-INFO +1 -1
- hermes_task_framework-1.3.0/hermes_task_framework/skills/compact-directory-memory/SKILL.md +167 -0
- hermes_task_framework-1.3.0/hermes_task_framework/skills/compact-directory-memory/scripts/manage_directory_context.py +127 -0
- hermes_task_framework-1.3.0/hermes_task_framework/skills/task-archaeology/SKILL.md +41 -0
- hermes_task_framework-1.3.0/hermes_task_framework/skills/task-artifact-integrity/SKILL.md +72 -0
- hermes_task_framework-1.3.0/hermes_task_framework/skills/task-artifact-integrity/references/task-root-relocation.md +31 -0
- hermes_task_framework-1.3.0/hermes_task_framework/skills/task-artifact-integrity/scripts/task_integrity.py +177 -0
- hermes_task_framework-1.3.0/hermes_task_framework/skills/task-aware-project-work/SKILL.md +42 -0
- hermes_task_framework-1.3.0/hermes_task_framework/skills/task-context-storage/SKILL.md +88 -0
- hermes_task_framework-1.3.0/hermes_task_framework/skills/task-cross-machine-sync/SKILL.md +67 -0
- hermes_task_framework-1.3.0/hermes_task_framework/skills/task-cross-machine-sync/scripts/task_git_sync.py +118 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/SKILL.md +102 -16
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/scripts/manage_task.py +82 -70
- hermes_task_framework-1.3.0/hermes_task_framework/skills/task-framework/scripts/task_api.py +195 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/scripts/task_ref.py +2 -2
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/scripts/update-index.py +3 -3
- hermes_task_framework-1.3.0/hermes_task_framework/skills/task-framework/templates/MEMORY.md +9 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/templates/TASK.md +3 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/templates/run.py +13 -13
- hermes_task_framework-1.3.0/hermes_task_framework/skills/task-lifecycle-discipline/SKILL.md +78 -0
- hermes_task_framework-1.3.0/hermes_task_framework/skills/task-lifecycle-edge-cases/references/task-5d5a1a-recovery-example.md +19 -0
- hermes_task_framework-1.3.0/hermes_task_framework/skills/task-lifecycle-portability/SKILL.md +66 -0
- hermes_task_framework-1.3.0/hermes_task_framework/skills/task-lifecycle-portability/references/output-model-design.md +26 -0
- hermes_task_framework-1.3.0/hermes_task_framework/tests/test_directory_context.py +58 -0
- hermes_task_framework-1.3.0/hermes_task_framework/tests/test_skill_ownership.py +62 -0
- hermes_task_framework-1.3.0/hermes_task_framework/tests/test_task_api.py +84 -0
- hermes_task_framework-1.3.0/hermes_task_framework/tests/test_task_git_sync.py +50 -0
- hermes_task_framework-1.3.0/hermes_task_framework/tests/test_task_integrity.py +70 -0
- hermes_task_framework-1.3.0/hermes_task_framework/tests/test_task_root_resolution.py +149 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework.egg-info/PKG-INFO +1 -1
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework.egg-info/SOURCES.txt +20 -5
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/pyproject.toml +1 -1
- hermes_task_framework-1.2.0/hermes_task_framework/skills/task-lifecycle-edge-cases/references/task-5d5a1a-recovery-example.md +0 -66
- hermes_task_framework-1.2.0/hermes_task_framework/skills/task-lifecycle-portability/SKILL.md +0 -115
- hermes_task_framework-1.2.0/hermes_task_framework/skills/task-lifecycle-portability/references/design-session-20260611.md +0 -31
- hermes_task_framework-1.2.0/hermes_task_framework/skills/task-lifecycle-portability/references/output-model-design.md +0 -55
- hermes_task_framework-1.2.0/hermes_task_framework/skills/task-lifecycle-portability/references/pipeline-output-transition.md +0 -41
- hermes_task_framework-1.2.0/hermes_task_framework/skills/task-lifecycle-portability/references/task-recovery-5d5a1a-example.md +0 -27
- hermes_task_framework-1.2.0/hermes_task_framework/skills/task-lifecycle-portability/references/task-recovery-procedure.md +0 -58
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/LICENSE +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/README.md +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/__init__.py +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-external-repos-pattern/SKILL.md +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/CHANGELOG.md +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/docs/TASKS.md +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/docs/architecture.dot +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/docs/architecture.svg +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/docs/product-requirements.md +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/references/auto-runner.md +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/references/composites/code-review-session.md +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/references/composites/research.md +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/references/composites/software-dev.md +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/references/document-analysis-workflow.md +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/references/file-safety-lesson.md +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/references/generating-output-documents.md +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/references/operations/code-write.md +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/references/operations/document-write.md +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/references/operations/info-search.md +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/references/operations/web-research.md +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/references/policy-time-metadata.md +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/references/research-workflow.md +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/references/task-format-validation.md +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/references/task-hash-naming.md +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/references/task-lifecycle-example.md +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/references/task-overview-discovery.md +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/references/task-types/analysis.md +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/references/task-types/external-audit.md +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/references/task-types/video-production-pipeline.md +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/scripts/__init__.py +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/scripts/convert_md_to_pdf.py +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/scripts/task-runner.sh +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-framework/templates/CHANGELOG.md +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-lifecycle-edge-cases/SKILL.md +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-timestamp-convention/SKILL.md +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework/skills/task-tracker/SKILL.md +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework.egg-info/dependency_links.txt +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/hermes_task_framework.egg-info/top_level.txt +0 -0
- {hermes_task_framework-1.2.0 → hermes_task_framework-1.3.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hermes-task-framework
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Structured task management skills for Hermes Agent — task creation, decomposition, stage gates
|
|
5
5
|
Project-URL: Source, https://github.com/nuffin/hermes-task-framework-pip
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
---
|
|
2
|
+
author: Hauzer S. Lee
|
|
3
|
+
category: software-development
|
|
4
|
+
description: Manage paired MEMORY.md/CHANGELOG.md in flat or nested directory contexts.
|
|
5
|
+
license: MIT
|
|
6
|
+
metadata:
|
|
7
|
+
hermes:
|
|
8
|
+
scenes:
|
|
9
|
+
- hermes
|
|
10
|
+
- coding
|
|
11
|
+
tags:
|
|
12
|
+
- memory
|
|
13
|
+
- changelog
|
|
14
|
+
- directory-context
|
|
15
|
+
- hierarchical-memory
|
|
16
|
+
- task-framework
|
|
17
|
+
relations:
|
|
18
|
+
- type: complemented_by
|
|
19
|
+
target: task-context-storage
|
|
20
|
+
properties:
|
|
21
|
+
reason: task-context-storage defines which persistence layer owns each fact
|
|
22
|
+
strength: strong
|
|
23
|
+
name: compact-directory-memory
|
|
24
|
+
platforms:
|
|
25
|
+
- linux
|
|
26
|
+
- macos
|
|
27
|
+
version: 3.0.0
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
# Compact Directory Memory
|
|
31
|
+
|
|
32
|
+
The authoritative format and maintenance skill for paired `MEMORY.md` and `CHANGELOG.md` files inside projects, tasks, and other directory-managed entities.
|
|
33
|
+
|
|
34
|
+
`task-framework` decides when task context must be loaded. This skill owns how directory context is structured, formatted, created, read, updated, and verified.
|
|
35
|
+
|
|
36
|
+
## Context pair
|
|
37
|
+
|
|
38
|
+
| File | Meaning | Update mode |
|
|
39
|
+
|------|---------|-------------|
|
|
40
|
+
| `MEMORY.md` | Stable expected-state facts, constraints, paths, interfaces, and conventions | Replace stale facts; keep compact |
|
|
41
|
+
| `CHANGELOG.md` | Chronological operations, decisions, verification, failures, and next steps | Append; never rewrite history |
|
|
42
|
+
|
|
43
|
+
Transient runtime status never belongs in `MEMORY.md`. Record observed execution results in `CHANGELOG.md`.
|
|
44
|
+
|
|
45
|
+
## MEMORY.md format
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
<one declarative fact paragraph>
|
|
49
|
+
|
|
50
|
+
§
|
|
51
|
+
|
|
52
|
+
<another declarative fact paragraph>
|
|
53
|
+
|
|
54
|
+
§
|
|
55
|
+
|
|
56
|
+
🔴 <critical invariant>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Rules:
|
|
60
|
+
|
|
61
|
+
- One fact per paragraph, separated by `§` on its own line.
|
|
62
|
+
- No headings, tables, tags, or frontmatter.
|
|
63
|
+
- Use declarative facts, not instructions.
|
|
64
|
+
- Use `🔴` only for enforced invariants.
|
|
65
|
+
- Remove or replace stale facts instead of retaining historical versions.
|
|
66
|
+
- Prefer expected configuration over transient observations.
|
|
67
|
+
- Keep reusable procedures in skills, not MEMORY.md.
|
|
68
|
+
|
|
69
|
+
## CHANGELOG.md format
|
|
70
|
+
|
|
71
|
+
Use chronological entries with the newest entry appended at the bottom:
|
|
72
|
+
|
|
73
|
+
```markdown
|
|
74
|
+
## YYYY-MM-DD HH:MM:SS
|
|
75
|
+
|
|
76
|
+
**Operation:** What happened.
|
|
77
|
+
**Reason:** Why this approach was selected.
|
|
78
|
+
**Artifacts:** Files, repositories, or outputs changed.
|
|
79
|
+
**Verification:** Commands/checks and real results.
|
|
80
|
+
**Blockers:** Unresolved problems, if any.
|
|
81
|
+
**Next step:** Exact continuation point for another session.
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Raw command output belongs in `output/logs/` or another log directory, not in CHANGELOG.md.
|
|
85
|
+
|
|
86
|
+
## Flat layout
|
|
87
|
+
|
|
88
|
+
```text
|
|
89
|
+
<entity>/
|
|
90
|
+
├── MEMORY.md
|
|
91
|
+
└── CHANGELOG.md
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Use for one project, one task, or one cohesive entity.
|
|
95
|
+
|
|
96
|
+
## Hierarchical layout
|
|
97
|
+
|
|
98
|
+
Use when multiple sessions modify distinct subsystems under one long-running task or project:
|
|
99
|
+
|
|
100
|
+
```text
|
|
101
|
+
<entity>/
|
|
102
|
+
├── MEMORY.md # root index and cross-subsystem facts
|
|
103
|
+
├── CHANGELOG.md # cross-subsystem chronological summaries
|
|
104
|
+
└── memories/
|
|
105
|
+
└── <sub-system>/
|
|
106
|
+
├── MEMORY.md # subsystem-owned stable facts
|
|
107
|
+
└── CHANGELOG.md # subsystem operations and verification
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Subsystem names are lowercase kebab-case. Every subsystem directory must contain both files.
|
|
111
|
+
|
|
112
|
+
### Root versus subsystem ownership
|
|
113
|
+
|
|
114
|
+
- Root `MEMORY.md`: entity identity, subsystem index, cross-subsystem invariants, shared interfaces.
|
|
115
|
+
- Root `CHANGELOG.md`: concise cross-subsystem summary with references to affected subsystem logs.
|
|
116
|
+
- Subsystem `MEMORY.md`: facts owned only by that subsystem.
|
|
117
|
+
- Subsystem `CHANGELOG.md`: detailed work history for that subsystem.
|
|
118
|
+
|
|
119
|
+
Root files are indexes and syntheses, not copies of subsystem files.
|
|
120
|
+
|
|
121
|
+
## Read protocol
|
|
122
|
+
|
|
123
|
+
Before changing a hierarchical entity:
|
|
124
|
+
|
|
125
|
+
1. Read root `MEMORY.md` completely.
|
|
126
|
+
2. Read recent relevant root `CHANGELOG.md` entries.
|
|
127
|
+
3. Identify the target subsystem.
|
|
128
|
+
4. Read its `MEMORY.md` completely.
|
|
129
|
+
5. Read recent relevant entries from its `CHANGELOG.md`.
|
|
130
|
+
|
|
131
|
+
## Write protocol
|
|
132
|
+
|
|
133
|
+
After a verified change:
|
|
134
|
+
|
|
135
|
+
1. Update subsystem `MEMORY.md` only if stable facts changed.
|
|
136
|
+
2. Append subsystem `CHANGELOG.md` with operation, reason, artifacts, verification, blockers, and next step.
|
|
137
|
+
3. Update root `MEMORY.md` only if the subsystem index or cross-subsystem facts changed.
|
|
138
|
+
4. Append a concise root `CHANGELOG.md` summary naming affected subsystem logs.
|
|
139
|
+
|
|
140
|
+
## Tooling
|
|
141
|
+
|
|
142
|
+
Create or extend a hierarchical context:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
python3 scripts/manage_directory_context.py init <entity-dir> <sub-system> [<sub-system> ...]
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Verify required files and naming:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
python3 scripts/manage_directory_context.py verify <entity-dir>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
The script never overwrites existing context files.
|
|
155
|
+
|
|
156
|
+
## Relationship to task-framework
|
|
157
|
+
|
|
158
|
+
`task-framework` owns task creation, status, checklist, indexes, and lifecycle. For task context it must:
|
|
159
|
+
|
|
160
|
+
1. Create root `MEMORY.md` and `CHANGELOG.md`.
|
|
161
|
+
2. Load this skill when hierarchical directory context is requested or detected.
|
|
162
|
+
3. Delegate nested creation, format rules, and validation to this skill.
|
|
163
|
+
4. Preserve `MEMORY.md`, `CHANGELOG.md`, and `memories/` during reset and cleanup.
|
|
164
|
+
|
|
165
|
+
## Relationship to changelog skill
|
|
166
|
+
|
|
167
|
+
The continuous `CHANGELOG.md` in this skill preserves cross-session context. The separate `changelog` skill creates immutable `changelog/<timestamp>.<topic>.md` delivery records. They are complementary and must not replace each other.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Create and verify flat or hierarchical directory context."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import re
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
SUBSYSTEM_RE = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")
|
|
12
|
+
|
|
13
|
+
ROOT_MEMORY = """Entity context is managed by compact-directory-memory.
|
|
14
|
+
|
|
15
|
+
§
|
|
16
|
+
|
|
17
|
+
Subsystem context index is stored under `memories/`.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
ROOT_CHANGELOG = """# CHANGELOG.md
|
|
21
|
+
|
|
22
|
+
Append chronological cross-session context entries.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
SUBSYSTEM_MEMORY = """Subsystem: `{subsystem}`.
|
|
26
|
+
|
|
27
|
+
§
|
|
28
|
+
|
|
29
|
+
Repository/path: <fill when known>.
|
|
30
|
+
|
|
31
|
+
§
|
|
32
|
+
|
|
33
|
+
Responsibilities and boundaries: <fill when known>.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
SUBSYSTEM_CHANGELOG = """# {subsystem} — CHANGELOG.md
|
|
37
|
+
|
|
38
|
+
Append chronological subsystem operations, decisions, verification, blockers, and next steps.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def ensure_file(path: Path, content: str) -> bool:
|
|
43
|
+
if path.exists():
|
|
44
|
+
return False
|
|
45
|
+
path.write_text(content, encoding="utf-8")
|
|
46
|
+
return True
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def validate_subsystem(name: str) -> None:
|
|
50
|
+
if not SUBSYSTEM_RE.fullmatch(name):
|
|
51
|
+
raise ValueError(f"invalid subsystem name: {name!r}; use lowercase kebab-case")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def init(entity_dir: Path, subsystems: list[str]) -> int:
|
|
55
|
+
if not entity_dir.is_dir():
|
|
56
|
+
print(f"ERROR: entity directory not found: {entity_dir}", file=sys.stderr)
|
|
57
|
+
return 2
|
|
58
|
+
created: list[Path] = []
|
|
59
|
+
if ensure_file(entity_dir / "MEMORY.md", ROOT_MEMORY):
|
|
60
|
+
created.append(entity_dir / "MEMORY.md")
|
|
61
|
+
if ensure_file(entity_dir / "CHANGELOG.md", ROOT_CHANGELOG):
|
|
62
|
+
created.append(entity_dir / "CHANGELOG.md")
|
|
63
|
+
memories = entity_dir / "memories"
|
|
64
|
+
memories.mkdir(exist_ok=True)
|
|
65
|
+
for subsystem in subsystems:
|
|
66
|
+
validate_subsystem(subsystem)
|
|
67
|
+
directory = memories / subsystem
|
|
68
|
+
directory.mkdir(exist_ok=True)
|
|
69
|
+
memory_file = directory / "MEMORY.md"
|
|
70
|
+
changelog_file = directory / "CHANGELOG.md"
|
|
71
|
+
if ensure_file(memory_file, SUBSYSTEM_MEMORY.format(subsystem=subsystem)):
|
|
72
|
+
created.append(memory_file)
|
|
73
|
+
if ensure_file(changelog_file, SUBSYSTEM_CHANGELOG.format(subsystem=subsystem)):
|
|
74
|
+
created.append(changelog_file)
|
|
75
|
+
for path in created:
|
|
76
|
+
print(f"created {path.relative_to(entity_dir)}")
|
|
77
|
+
print(f"initialized directory context: subsystems={len(subsystems)}")
|
|
78
|
+
return verify(entity_dir)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def verify(entity_dir: Path) -> int:
|
|
82
|
+
errors: list[str] = []
|
|
83
|
+
for required in ("MEMORY.md", "CHANGELOG.md"):
|
|
84
|
+
if not (entity_dir / required).is_file():
|
|
85
|
+
errors.append(f"missing root file: {required}")
|
|
86
|
+
memories = entity_dir / "memories"
|
|
87
|
+
if not memories.is_dir():
|
|
88
|
+
errors.append("missing directory: memories/")
|
|
89
|
+
else:
|
|
90
|
+
for directory in sorted(path for path in memories.iterdir() if path.is_dir()):
|
|
91
|
+
try:
|
|
92
|
+
validate_subsystem(directory.name)
|
|
93
|
+
except ValueError as exc:
|
|
94
|
+
errors.append(str(exc))
|
|
95
|
+
for required in ("MEMORY.md", "CHANGELOG.md"):
|
|
96
|
+
if not (directory / required).is_file():
|
|
97
|
+
errors.append(f"missing subsystem file: memories/{directory.name}/{required}")
|
|
98
|
+
if errors:
|
|
99
|
+
for error in errors:
|
|
100
|
+
print(f"ERROR: {error}", file=sys.stderr)
|
|
101
|
+
return 1
|
|
102
|
+
subsystem_count = sum(1 for path in memories.iterdir() if path.is_dir())
|
|
103
|
+
print(f"verified directory context: root files OK, subsystems={subsystem_count}")
|
|
104
|
+
return 0
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def main() -> int:
|
|
108
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
109
|
+
commands = parser.add_subparsers(dest="command", required=True)
|
|
110
|
+
init_parser = commands.add_parser("init")
|
|
111
|
+
init_parser.add_argument("entity_dir", type=Path)
|
|
112
|
+
init_parser.add_argument("subsystems", nargs="*")
|
|
113
|
+
verify_parser = commands.add_parser("verify")
|
|
114
|
+
verify_parser.add_argument("entity_dir", type=Path)
|
|
115
|
+
args = parser.parse_args()
|
|
116
|
+
entity_dir = args.entity_dir.expanduser().resolve()
|
|
117
|
+
try:
|
|
118
|
+
if args.command == "init":
|
|
119
|
+
return init(entity_dir, args.subsystems)
|
|
120
|
+
return verify(entity_dir)
|
|
121
|
+
except ValueError as exc:
|
|
122
|
+
print(f"ERROR: {exc}", file=sys.stderr)
|
|
123
|
+
return 2
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
if __name__ == "__main__":
|
|
127
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
author: Hermes Agent
|
|
3
|
+
category: software-development
|
|
4
|
+
description: Reconstruct missing session context from canonical task artifacts and repository history.
|
|
5
|
+
license: MIT
|
|
6
|
+
metadata:
|
|
7
|
+
hermes:
|
|
8
|
+
scenes: [hermes, research]
|
|
9
|
+
tags: [task-framework, archaeology, session-recovery, task-recovery, forensics]
|
|
10
|
+
relations:
|
|
11
|
+
- type: depends_on
|
|
12
|
+
target: task-aware-project-work
|
|
13
|
+
properties: {reason: canonical task context must be read in order, strength: strong}
|
|
14
|
+
- type: complemented_by
|
|
15
|
+
target: task-artifact-integrity
|
|
16
|
+
properties: {reason: validates recovered artifact closure, strength: strong}
|
|
17
|
+
name: task-archaeology
|
|
18
|
+
platforms: [linux, macos]
|
|
19
|
+
version: 2.0.0
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
# Task Archaeology
|
|
23
|
+
|
|
24
|
+
Use when session history is missing or incomplete but the user provides a task name, hash, or directory.
|
|
25
|
+
|
|
26
|
+
## Recovery order
|
|
27
|
+
|
|
28
|
+
1. Resolve the task with `task_api.py describe <identifier>`; do not pass task directory names as session IDs.
|
|
29
|
+
2. Read `TASK.md`, root `MEMORY.md`, and recent root `CHANGELOG.md`.
|
|
30
|
+
3. When hierarchical, read each relevant subsystem MEMORY and recent CHANGELOG.
|
|
31
|
+
4. Read `README.md` and `.hermes-task.json` for creation time, outputs, and relationships.
|
|
32
|
+
5. Resolve dependencies, related tasks, superseded tasks, and named outputs.
|
|
33
|
+
6. Inventory `input/`, `output/docs/`, `output/logs/`, and scripts.
|
|
34
|
+
7. Cross-reference repository branches and commits by timestamps and artifact paths.
|
|
35
|
+
8. State what is directly evidenced, inferred, missing, and still blocked.
|
|
36
|
+
|
|
37
|
+
Task artifacts are evidence of task state, not a verbatim transcript. Never invent user statements from file outcomes.
|
|
38
|
+
|
|
39
|
+
## Result
|
|
40
|
+
|
|
41
|
+
Return the reconstructed goal, decisions, produced artifacts, verification, unresolved issues, and exact continuation point, with file paths for every claim.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
author: Hermes Agent
|
|
3
|
+
category: software-development
|
|
4
|
+
description: Validate task context closure, artifacts, references, and safe relocation before continuation or deletion.
|
|
5
|
+
license: MIT
|
|
6
|
+
metadata:
|
|
7
|
+
hermes:
|
|
8
|
+
scenes: [hermes, coding, research]
|
|
9
|
+
tags: [task-framework, artifact-integrity, relocation, audit, task-recovery]
|
|
10
|
+
relations:
|
|
11
|
+
- type: depends_on
|
|
12
|
+
target: task-framework
|
|
13
|
+
properties: {reason: task-framework defines canonical task files and references, strength: strong}
|
|
14
|
+
- type: complemented_by
|
|
15
|
+
target: compact-directory-memory
|
|
16
|
+
properties: {reason: hierarchical context files are part of task closure, strength: strong}
|
|
17
|
+
name: task-artifact-integrity
|
|
18
|
+
platforms: [linux, macos]
|
|
19
|
+
version: 2.0.0
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
# Task Artifact Integrity
|
|
23
|
+
|
|
24
|
+
Use when relocating a task root, consolidating related tasks, diagnosing an apparently empty task, or validating that another session can continue safely.
|
|
25
|
+
|
|
26
|
+
## Context closure
|
|
27
|
+
|
|
28
|
+
A complete task closure includes:
|
|
29
|
+
|
|
30
|
+
- `TASK.md`, `README.md`, `MEMORY.md`, `CHANGELOG.md`, `.hermes-task.json`;
|
|
31
|
+
- `memories/<sub-system>/MEMORY.md` and `CHANGELOG.md` pairs when hierarchical;
|
|
32
|
+
- `input/`, `output/`, and `scripts/` inventories;
|
|
33
|
+
- named outputs, dependencies, related/superseded tasks, ticket/project extension fields;
|
|
34
|
+
- absolute paths and cross-task references.
|
|
35
|
+
|
|
36
|
+
## Inspection
|
|
37
|
+
|
|
38
|
+
1. Resolve the task through `task_api.py describe`.
|
|
39
|
+
2. Read root and target subsystem context.
|
|
40
|
+
3. Inventory source inputs separately from generated output.
|
|
41
|
+
4. Resolve every related task and named output.
|
|
42
|
+
5. Report missing, empty, stale, or external references before mutation.
|
|
43
|
+
|
|
44
|
+
## Safe relocation
|
|
45
|
+
|
|
46
|
+
1. Confirm exact source and destination roots.
|
|
47
|
+
2. Refuse overwrite when destination exists.
|
|
48
|
+
3. Copy metadata-preserving content without `--delete`.
|
|
49
|
+
4. Compare relative path, type, size, and SHA-256 manifests.
|
|
50
|
+
5. Rewrite absolute references only after copy verification.
|
|
51
|
+
6. Append relocation entries to affected CHANGELOG files.
|
|
52
|
+
7. Regenerate both roots' indexes.
|
|
53
|
+
8. Remove source only after equality and reference verification.
|
|
54
|
+
|
|
55
|
+
Same-machine relocation needs no archive unless requested. Cross-machine transfer or explicit rollback may retain an archive.
|
|
56
|
+
|
|
57
|
+
## Completion evidence
|
|
58
|
+
|
|
59
|
+
Report task hashes, roots, input/output inventories, manifest equality, context-pair verification, rewritten references, index regeneration, and source/archive status.
|
|
60
|
+
|
|
61
|
+
See `references/task-root-relocation.md` for the checklist.
|
|
62
|
+
|
|
63
|
+
## Permanent tooling
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
python3 scripts/task_integrity.py audit <hash-or-dir>
|
|
67
|
+
python3 scripts/task_integrity.py closure <hash-or-dir>
|
|
68
|
+
python3 scripts/task_integrity.py manifest <hash-or-dir> --output <manifest.json>
|
|
69
|
+
python3 scripts/task_integrity.py compare <source-dir> <destination-dir>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
All commands emit JSON and return nonzero when integrity or equality fails.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Task root relocation checklist
|
|
2
|
+
|
|
3
|
+
## Invariant
|
|
4
|
+
|
|
5
|
+
After relocation, canonical files, root and subsystem context, source inputs, generated evidence, named outputs, and related-task references remain resolvable without an undeclared old root.
|
|
6
|
+
|
|
7
|
+
## Required checks
|
|
8
|
+
|
|
9
|
+
| Check | Evidence | Failure response |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| Canonical files | TASK, README, MEMORY, CHANGELOG, metadata are nonempty | Restore/report before source deletion |
|
|
12
|
+
| Hierarchical context | Every subsystem directory has both context files | Repair complete pair before relocation |
|
|
13
|
+
| Source input | `input/` inventory | Locate source owners; never infer emptiness is acceptable |
|
|
14
|
+
| Generated evidence | `output/` and review/log inventory | Preserve evidence or owning task |
|
|
15
|
+
| Related closure | metadata and document references resolve | Move closure or rewrite verified references |
|
|
16
|
+
| Named outputs | every registered output resolves | Repair metadata/path before completion |
|
|
17
|
+
| Copy equality | relative path/type/size/SHA-256 manifests match | Retain source and fail relocation |
|
|
18
|
+
| Indexes | destination and source indexes regenerated | Do not report completion |
|
|
19
|
+
|
|
20
|
+
## Record
|
|
21
|
+
|
|
22
|
+
```markdown
|
|
23
|
+
## YYYY-MM-DD HH:MM:SS TZ
|
|
24
|
+
|
|
25
|
+
**Operation:** Relocated task `<hash>` from `<old-root>` to `<new-root>`.
|
|
26
|
+
**Context closure:** `<related hashes and subsystems>`.
|
|
27
|
+
**Verification:** `<file count and manifest equality>`.
|
|
28
|
+
**References:** `<updated paths or none>`.
|
|
29
|
+
**Indexes:** `<old/new regeneration result>`.
|
|
30
|
+
**Source status:** `<removed after verification / retained>`.
|
|
31
|
+
```
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Audit task closure and compare deterministic task manifests."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import hashlib
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
TASK_SCRIPTS = Path(__file__).resolve().parents[2] / "task-framework" / "scripts"
|
|
14
|
+
sys.path.insert(0, str(TASK_SCRIPTS))
|
|
15
|
+
import task_api # pyright: ignore[reportMissingImports] # noqa: E402
|
|
16
|
+
|
|
17
|
+
CANONICAL = ("TASK.md", "README.md", "MEMORY.md", "CHANGELOG.md", ".hermes-task.json")
|
|
18
|
+
RELATION_KEYS = ("dependencies", "related", "supersedes", "requires", "required_by")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def file_hash(path: Path) -> str:
|
|
22
|
+
digest = hashlib.sha256()
|
|
23
|
+
with path.open("rb") as handle:
|
|
24
|
+
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
|
25
|
+
digest.update(chunk)
|
|
26
|
+
return digest.hexdigest()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def build_manifest(root: Path) -> list[dict]:
|
|
30
|
+
entries: list[dict] = []
|
|
31
|
+
for path in sorted(root.rglob("*")):
|
|
32
|
+
relative = path.relative_to(root).as_posix()
|
|
33
|
+
if relative == ".git" or relative.startswith(".git/"):
|
|
34
|
+
continue
|
|
35
|
+
if path.is_symlink():
|
|
36
|
+
entries.append({"path": relative, "type": "symlink", "target": os.readlink(path)})
|
|
37
|
+
elif path.is_dir():
|
|
38
|
+
entries.append({"path": relative, "type": "directory"})
|
|
39
|
+
elif path.is_file():
|
|
40
|
+
entries.append(
|
|
41
|
+
{"path": relative, "type": "file", "size": path.stat().st_size, "sha256": file_hash(path)}
|
|
42
|
+
)
|
|
43
|
+
return entries
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def relation_hashes(metadata: dict) -> list[str]:
|
|
47
|
+
values: list[str] = []
|
|
48
|
+
for key in RELATION_KEYS:
|
|
49
|
+
raw = metadata.get(key, [])
|
|
50
|
+
if isinstance(raw, list):
|
|
51
|
+
values.extend(str(item) for item in raw if item)
|
|
52
|
+
return sorted(set(values))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def audit(identifier: str) -> dict:
|
|
56
|
+
description = task_api.command_describe(identifier)
|
|
57
|
+
root = Path(description["path"])
|
|
58
|
+
metadata = description["metadata"]
|
|
59
|
+
errors: list[str] = []
|
|
60
|
+
warnings: list[str] = []
|
|
61
|
+
for name in CANONICAL:
|
|
62
|
+
path = root / name
|
|
63
|
+
if not path.is_file():
|
|
64
|
+
errors.append(f"missing canonical file: {name}")
|
|
65
|
+
elif path.stat().st_size == 0:
|
|
66
|
+
errors.append(f"empty canonical file: {name}")
|
|
67
|
+
memories = root / "memories"
|
|
68
|
+
hierarchical = metadata.get("memory_layout") == "hierarchical" or memories.is_dir()
|
|
69
|
+
subsystems: list[str] = []
|
|
70
|
+
if hierarchical:
|
|
71
|
+
if not memories.is_dir():
|
|
72
|
+
errors.append("hierarchical metadata set but memories/ is missing")
|
|
73
|
+
else:
|
|
74
|
+
for directory in sorted(path for path in memories.iterdir() if path.is_dir()):
|
|
75
|
+
subsystems.append(directory.name)
|
|
76
|
+
for name in ("MEMORY.md", "CHANGELOG.md"):
|
|
77
|
+
if not (directory / name).is_file():
|
|
78
|
+
errors.append(f"incomplete subsystem context: memories/{directory.name}/{name}")
|
|
79
|
+
outputs = metadata.get("outputs", {})
|
|
80
|
+
if isinstance(outputs, dict):
|
|
81
|
+
for name, raw_path in outputs.items():
|
|
82
|
+
candidate = root / str(raw_path)
|
|
83
|
+
if not candidate.exists():
|
|
84
|
+
errors.append(f"named output does not resolve: {name} -> {raw_path}")
|
|
85
|
+
unresolved: list[str] = []
|
|
86
|
+
for task_hash in relation_hashes(metadata):
|
|
87
|
+
try:
|
|
88
|
+
task_api.command_describe(task_hash)
|
|
89
|
+
except FileNotFoundError:
|
|
90
|
+
unresolved.append(task_hash)
|
|
91
|
+
if unresolved:
|
|
92
|
+
errors.append(f"unresolved related task hashes: {', '.join(unresolved)}")
|
|
93
|
+
for dirname in ("input", "output", "scripts"):
|
|
94
|
+
if not (root / dirname).is_dir():
|
|
95
|
+
warnings.append(f"missing standard directory: {dirname}/")
|
|
96
|
+
return {
|
|
97
|
+
"task": description,
|
|
98
|
+
"ok": not errors,
|
|
99
|
+
"errors": errors,
|
|
100
|
+
"warnings": warnings,
|
|
101
|
+
"subsystems": subsystems,
|
|
102
|
+
"relations": relation_hashes(metadata),
|
|
103
|
+
"manifest_entries": len(build_manifest(root)),
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def closure(identifier: str) -> dict:
|
|
108
|
+
root = task_api.command_describe(identifier)
|
|
109
|
+
queue = [root["hash"]]
|
|
110
|
+
seen: dict[str, dict] = {}
|
|
111
|
+
unresolved: list[str] = []
|
|
112
|
+
while queue:
|
|
113
|
+
current = queue.pop(0)
|
|
114
|
+
if current in seen:
|
|
115
|
+
continue
|
|
116
|
+
try:
|
|
117
|
+
item = task_api.command_describe(current)
|
|
118
|
+
except FileNotFoundError:
|
|
119
|
+
unresolved.append(current)
|
|
120
|
+
continue
|
|
121
|
+
seen[current] = item
|
|
122
|
+
queue.extend(task_hash for task_hash in relation_hashes(item["metadata"]) if task_hash not in seen)
|
|
123
|
+
return {"root": root["hash"], "tasks": list(seen.values()), "unresolved": sorted(set(unresolved))}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def compare(source: Path, destination: Path) -> dict:
|
|
127
|
+
source_manifest = build_manifest(source.resolve())
|
|
128
|
+
destination_manifest = build_manifest(destination.resolve())
|
|
129
|
+
source_map = {entry["path"]: entry for entry in source_manifest}
|
|
130
|
+
destination_map = {entry["path"]: entry for entry in destination_manifest}
|
|
131
|
+
missing = sorted(set(source_map) - set(destination_map))
|
|
132
|
+
extra = sorted(set(destination_map) - set(source_map))
|
|
133
|
+
changed = sorted(
|
|
134
|
+
path for path in set(source_map) & set(destination_map) if source_map[path] != destination_map[path]
|
|
135
|
+
)
|
|
136
|
+
return {"equal": not missing and not extra and not changed, "missing": missing, "extra": extra, "changed": changed}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def main() -> int:
|
|
140
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
141
|
+
commands = parser.add_subparsers(dest="command", required=True)
|
|
142
|
+
audit_parser = commands.add_parser("audit")
|
|
143
|
+
audit_parser.add_argument("identifier")
|
|
144
|
+
closure_parser = commands.add_parser("closure")
|
|
145
|
+
closure_parser.add_argument("identifier")
|
|
146
|
+
manifest_parser = commands.add_parser("manifest")
|
|
147
|
+
manifest_parser.add_argument("identifier")
|
|
148
|
+
manifest_parser.add_argument("--output", type=Path)
|
|
149
|
+
compare_parser = commands.add_parser("compare")
|
|
150
|
+
compare_parser.add_argument("source", type=Path)
|
|
151
|
+
compare_parser.add_argument("destination", type=Path)
|
|
152
|
+
args = parser.parse_args()
|
|
153
|
+
try:
|
|
154
|
+
if args.command == "audit":
|
|
155
|
+
result = audit(args.identifier)
|
|
156
|
+
code = 0 if result["ok"] else 1
|
|
157
|
+
elif args.command == "closure":
|
|
158
|
+
result = closure(args.identifier)
|
|
159
|
+
code = 0 if not result["unresolved"] else 1
|
|
160
|
+
elif args.command == "manifest":
|
|
161
|
+
root = Path(task_api.command_describe(args.identifier)["path"])
|
|
162
|
+
result = build_manifest(root)
|
|
163
|
+
if args.output:
|
|
164
|
+
args.output.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
165
|
+
code = 0
|
|
166
|
+
else:
|
|
167
|
+
result = compare(args.source, args.destination)
|
|
168
|
+
code = 0 if result["equal"] else 1
|
|
169
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
170
|
+
return code
|
|
171
|
+
except (FileNotFoundError, json.JSONDecodeError, OSError) as exc:
|
|
172
|
+
print(json.dumps({"error": str(exc)}, ensure_ascii=False), file=sys.stderr)
|
|
173
|
+
return 2
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
if __name__ == "__main__":
|
|
177
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
author: Hermes Agent
|
|
3
|
+
category: software-development
|
|
4
|
+
description: Load canonical root and subsystem task context before continuing project work.
|
|
5
|
+
license: MIT
|
|
6
|
+
metadata:
|
|
7
|
+
hermes:
|
|
8
|
+
scenes: [hermes, coding]
|
|
9
|
+
tags: [task-framework, task-context, discovery, continuation, project-management]
|
|
10
|
+
relations:
|
|
11
|
+
- type: depends_on
|
|
12
|
+
target: task-context-storage
|
|
13
|
+
properties: {reason: defines context ownership, strength: strong}
|
|
14
|
+
- type: depends_on
|
|
15
|
+
target: compact-directory-memory
|
|
16
|
+
properties: {reason: defines hierarchical read protocol, strength: strong}
|
|
17
|
+
name: task-aware-project-work
|
|
18
|
+
platforms: [linux, macos]
|
|
19
|
+
version: 2.0.0
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
# Task-Aware Project Work
|
|
23
|
+
|
|
24
|
+
Use when continuing a task by name/hash, returning to project work tied to a task, or before creating project artifacts that may already exist.
|
|
25
|
+
|
|
26
|
+
## Protocol
|
|
27
|
+
|
|
28
|
+
1. Resolve the task with `task_api.py describe <identifier>`.
|
|
29
|
+
2. Read `TASK.md`, root `MEMORY.md`, and recent root `CHANGELOG.md`.
|
|
30
|
+
3. Detect `.hermes-task.json.memory_layout` or `memories/`.
|
|
31
|
+
4. Identify the target subsystem and read its full MEMORY plus recent CHANGELOG.
|
|
32
|
+
5. Resolve project/repository paths from task context and verify existing artifacts before creating anything.
|
|
33
|
+
6. If a hierarchical subsystem is missing, load `compact-directory-memory` and create the complete pair before source changes.
|
|
34
|
+
|
|
35
|
+
Legacy `TASK_MEMORY.md` is not canonical. Use it only as archaeological evidence when canonical files are absent.
|
|
36
|
+
|
|
37
|
+
## Pitfalls
|
|
38
|
+
|
|
39
|
+
- A ticket is not a task-framework task.
|
|
40
|
+
- Never start implementation before loading task context.
|
|
41
|
+
- Search actual repositories before concluding an artifact was deleted.
|
|
42
|
+
- Do not duplicate a project or subsystem because another session used a different path.
|