mirror of
https://github.com/khodges42/nightShift.git
synced 2026-06-14 10:08:37 +00:00
Clean up some files and add a new tutorial
This commit is contained in:
parent
71e65a1df4
commit
5e5cd184b9
|
|
@ -1,84 +0,0 @@
|
||||||
# Project Context Chart
|
|
||||||
|
|
||||||
## Entry Points
|
|
||||||
|
|
||||||
- `tests/test_lisp.py`: class SmokeTests(unittest.TestCase):
|
|
||||||
|
|
||||||
## Tests
|
|
||||||
|
|
||||||
- `tests/__init__.py`
|
|
||||||
- `tests/test_lisp.py`
|
|
||||||
|
|
||||||
## Files
|
|
||||||
|
|
||||||
### `agents/implementer.md`
|
|
||||||
|
|
||||||
- Responsibility: You are the implementation agent for NightShift.
|
|
||||||
- Entry point: false
|
|
||||||
- Test file: false
|
|
||||||
- Functions: None detected
|
|
||||||
- Classes: None detected
|
|
||||||
- Anchors/search terms: None detected
|
|
||||||
|
|
||||||
### `agents/planner.md`
|
|
||||||
|
|
||||||
- Responsibility: You are the planning agent for NightShift.
|
|
||||||
- Entry point: false
|
|
||||||
- Test file: false
|
|
||||||
- Functions: None detected
|
|
||||||
- Classes: None detected
|
|
||||||
- Anchors/search terms: None detected
|
|
||||||
|
|
||||||
### `agents/reviewer.md`
|
|
||||||
|
|
||||||
- Responsibility: You are the review agent for NightShift.
|
|
||||||
- Entry point: false
|
|
||||||
- Test file: false
|
|
||||||
- Functions: None detected
|
|
||||||
- Classes: None detected
|
|
||||||
- Anchors/search terms: None detected
|
|
||||||
|
|
||||||
### `lisp.py`
|
|
||||||
|
|
||||||
- Responsibility: """Tiny Lisp quickstart target.
|
|
||||||
- Entry point: false
|
|
||||||
- Test file: false
|
|
||||||
- Functions: None detected
|
|
||||||
- Classes: None detected
|
|
||||||
- Anchors/search terms: None detected
|
|
||||||
|
|
||||||
### `nightshift.yaml`
|
|
||||||
|
|
||||||
- Responsibility: project:
|
|
||||||
- Entry point: false
|
|
||||||
- Test file: false
|
|
||||||
- Functions: None detected
|
|
||||||
- Classes: None detected
|
|
||||||
- Anchors/search terms: None detected
|
|
||||||
|
|
||||||
### `tasks.md`
|
|
||||||
|
|
||||||
- Responsibility: Tasks
|
|
||||||
- Entry point: false
|
|
||||||
- Test file: false
|
|
||||||
- Functions: None detected
|
|
||||||
- Classes: None detected
|
|
||||||
- Anchors/search terms: None detected
|
|
||||||
|
|
||||||
### `tests/__init__.py`
|
|
||||||
|
|
||||||
- Responsibility: """Quickstart Lisp test suite."""
|
|
||||||
- Entry point: false
|
|
||||||
- Test file: true
|
|
||||||
- Functions: None detected
|
|
||||||
- Classes: None detected
|
|
||||||
- Anchors/search terms: None detected
|
|
||||||
|
|
||||||
### `tests/test_lisp.py`
|
|
||||||
|
|
||||||
- Responsibility: class SmokeTests(unittest.TestCase):
|
|
||||||
- Entry point: true
|
|
||||||
- Test file: true
|
|
||||||
- Functions: test_smoke@L5
|
|
||||||
- Classes: SmokeTests@L4
|
|
||||||
- Anchors/search terms: SmokeTests, test_smoke
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
# Project Context
|
|
||||||
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
project:
|
|
||||||
name: tiny-lisp
|
|
||||||
root: .
|
|
||||||
task_file: tasks.md
|
|
||||||
artifact_dir: .nightshift
|
|
||||||
|
|
||||||
safety:
|
|
||||||
require_clean_worktree: false
|
|
||||||
scoped_paths:
|
|
||||||
- .
|
|
||||||
allowed_commands:
|
|
||||||
- python -m unittest discover -v
|
|
||||||
forbidden_commands:
|
|
||||||
- rm -rf
|
|
||||||
- git push
|
|
||||||
- curl | bash
|
|
||||||
|
|
||||||
agents:
|
|
||||||
planner:
|
|
||||||
backend: ollama
|
|
||||||
model: qwen2.5-coder:14b
|
|
||||||
temperature: 0.2
|
|
||||||
system_prompt: agents/planner.md
|
|
||||||
|
|
||||||
implementer:
|
|
||||||
backend: ollama
|
|
||||||
model: qwen2.5-coder:14b
|
|
||||||
temperature: 0.1
|
|
||||||
system_prompt: agents/implementer.md
|
|
||||||
|
|
||||||
reviewer:
|
|
||||||
backend: ollama
|
|
||||||
model: qwen2.5-coder:14b
|
|
||||||
temperature: 0.1
|
|
||||||
system_prompt: agents/reviewer.md
|
|
||||||
|
|
||||||
pipeline:
|
|
||||||
max_task_retries: 3
|
|
||||||
continue_on_task_failure: false
|
|
||||||
stages:
|
|
||||||
- id: plan
|
|
||||||
type: agent
|
|
||||||
agent: planner
|
|
||||||
output: plan.md
|
|
||||||
|
|
||||||
- id: context
|
|
||||||
type: repo_context
|
|
||||||
output: context-pack.md
|
|
||||||
|
|
||||||
- id: implement
|
|
||||||
type: code_writer
|
|
||||||
agent: implementer
|
|
||||||
output: proposed.patch
|
|
||||||
|
|
||||||
- id: normalize
|
|
||||||
type: patch_normalizer
|
|
||||||
output: normalized.patch
|
|
||||||
|
|
||||||
- id: validate_patch
|
|
||||||
type: patch_validator
|
|
||||||
output: patch-validation.md
|
|
||||||
max_files: 4
|
|
||||||
max_lines: 400
|
|
||||||
on_fail: implement
|
|
||||||
|
|
||||||
- id: apply_patch
|
|
||||||
type: patch_apply
|
|
||||||
mode: apply
|
|
||||||
output: patch-apply-output.txt
|
|
||||||
on_fail: implement
|
|
||||||
|
|
||||||
- id: test
|
|
||||||
type: command
|
|
||||||
commands:
|
|
||||||
- python -m unittest discover -v
|
|
||||||
output: test-output.txt
|
|
||||||
shell: true
|
|
||||||
timeout_seconds: 60
|
|
||||||
on_fail: implement
|
|
||||||
|
|
||||||
- id: review
|
|
||||||
type: agent_review
|
|
||||||
agent: reviewer
|
|
||||||
on_fail: implement
|
|
||||||
output: review.md
|
|
||||||
|
|
||||||
- id: summarize
|
|
||||||
type: summarize
|
|
||||||
output: final-notes.md
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
You are the implementation agent for NightShift.
|
|
||||||
|
|
||||||
Output only a unified diff.
|
|
||||||
Do not wrap the patch in markdown fences.
|
|
||||||
Do not include explanations before or after the patch.
|
|
||||||
Use diff --git headers.
|
|
||||||
Include tests when needed.
|
|
||||||
Keep the change as small as possible.
|
|
||||||
Only edit files needed for the task.
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
You are the planning agent for NightShift.
|
|
||||||
|
|
||||||
Create a concise implementation plan for the current task.
|
|
||||||
|
|
||||||
If you need repository context before planning, output lookup requests exactly like this:
|
|
||||||
|
|
||||||
lookup_requests:
|
|
||||||
- tool: read_file
|
|
||||||
path: relative/path.py
|
|
||||||
- tool: grep
|
|
||||||
path: .
|
|
||||||
pattern: search_regex
|
|
||||||
|
|
||||||
After context is provided, write a short plan with:
|
|
||||||
- files to edit
|
|
||||||
- tests to add or update
|
|
||||||
- risks
|
|
||||||
|
|
||||||
Do not write code.
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
You are the review agent for NightShift.
|
|
||||||
|
|
||||||
Review the task, plan, patch artifacts, test output, and final state.
|
|
||||||
|
|
||||||
Output exactly:
|
|
||||||
|
|
||||||
status: pass | fail | retry | escalate
|
|
||||||
reason: <short explanation>
|
|
||||||
next_stage: <optional stage id>
|
|
||||||
context_update: <compact useful note>
|
|
||||||
|
|
||||||
Use retry when the implementation is close but needs another patch.
|
|
||||||
Use fail when the patch is unsafe, unrelated, or clearly broken.
|
|
||||||
Use pass only when the acceptance criteria are satisfied.
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
# Run Metadata
|
|
||||||
|
|
||||||
Project: tiny-lisp
|
|
||||||
Experiment label:
|
|
||||||
Prompt variant:
|
|
||||||
|
|
||||||
## Agents
|
|
||||||
|
|
||||||
### planner
|
|
||||||
|
|
||||||
- Backend: ollama
|
|
||||||
- Model: qwen2.5-coder:14b
|
|
||||||
- Temperature: 0.2
|
|
||||||
- Base URL:
|
|
||||||
- Command:
|
|
||||||
- System prompt: agents\planner.md
|
|
||||||
|
|
||||||
### implementer
|
|
||||||
|
|
||||||
- Backend: ollama
|
|
||||||
- Model: qwen2.5-coder:14b
|
|
||||||
- Temperature: 0.1
|
|
||||||
- Base URL:
|
|
||||||
- Command:
|
|
||||||
- System prompt: agents\implementer.md
|
|
||||||
|
|
||||||
### reviewer
|
|
||||||
|
|
||||||
- Backend: ollama
|
|
||||||
- Model: qwen2.5-coder:14b
|
|
||||||
- Temperature: 0.1
|
|
||||||
- Base URL:
|
|
||||||
- Command:
|
|
||||||
- System prompt: agents\reviewer.md
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
# Run Summary
|
|
||||||
|
|
||||||
- Task: TASK-001
|
|
||||||
- Status: failed
|
|
||||||
- Retry count: 3
|
|
||||||
- Reason: Retry limit reached after stage 'apply_patch': Patch apply failed with code 128.
|
|
||||||
- Experiment label:
|
|
||||||
- Prompt variant:
|
|
||||||
|
|
||||||
## Modified Files
|
|
||||||
|
|
||||||
- Unavailable or none detected
|
|
||||||
|
|
||||||
## Artifacts
|
|
||||||
|
|
||||||
- Final notes: `tasks\TASK-001\final-notes.md`
|
|
||||||
- Stage results: `tasks\TASK-001\stage-results.md`
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
diff --git a/lisp.py b/lisp.py
|
|
||||||
index 0000000..f2a5c3d
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/lisp.py
|
|
||||||
@@ -0,0 +1,78 @@
|
|
||||||
+import re
|
|
||||||
+
|
|
||||||
+class LispParser:
|
|
||||||
+ def __init__(self, expression):
|
|
||||||
+ self.expression = expression
|
|
||||||
+ self.tokens = self.tokenize()
|
|
||||||
+ self.index = 0
|
|
||||||
+
|
|
||||||
+ def tokenize(self):
|
|
||||||
+ token_pattern = r'\s*([()])|(\d+)|([a-zA-Z_][a-zA-Z0-9_]*)'
|
|
||||||
+ tokens = re.findall(token_pattern, self.expression)
|
|
||||||
+ return [token for group in tokens for token in group if token]
|
|
||||||
+
|
|
||||||
+ def parse(self):
|
|
||||||
+ result = []
|
|
||||||
+ while self.index < len(self.tokens):
|
|
||||||
+ token = self.tokens[self.index]
|
|
||||||
+ if token == '(':
|
|
||||||
+ self.index += 1
|
|
||||||
+ result.append(self.parse())
|
|
||||||
+ elif token == ')':
|
|
||||||
+ return result
|
|
||||||
+ else:
|
|
||||||
+ result.append(self.parse_atom(token))
|
|
||||||
+ self.index += 1
|
|
||||||
+ if len(result) == 1:
|
|
||||||
+ return result[0]
|
|
||||||
+ return result
|
|
||||||
+
|
|
||||||
+ def parse_atom(self, token):
|
|
||||||
+ try:
|
|
||||||
+ return int(token)
|
|
||||||
+ except ValueError:
|
|
||||||
+ return token
|
|
||||||
+
|
|
||||||
+def parse_lisp(expression):
|
|
||||||
+ parser = LispParser(expression)
|
|
||||||
+ return parser.parse()
|
|
||||||
+
|
|
||||||
+if __name__ == "__main__":
|
|
||||||
+ expression = "(1 2 (3 4) a b)"
|
|
||||||
+ print(parse_lisp(expression))
|
|
||||||
diff --git a/tests/test_lisp.py b/tests/test_lisp.py
|
|
||||||
index 0000000..b5c7d8e
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/tests/test_lisp.py
|
|
||||||
@@ -0,0 +1,39 @@
|
|
||||||
+import unittest
|
|
||||||
+from lisp import parse_lisp
|
|
||||||
+
|
|
||||||
+class TestLispParser(unittest.TestCase):
|
|
||||||
+ def test_parse_numbers(self):
|
|
||||||
+ self.assertEqual(parse_lisp("123"), 123)
|
|
||||||
+ self.assertEqual(parse_lisp("(1 2 3)"), [1, 2, 3])
|
|
||||||
+
|
|
||||||
+ def test_parse_symbols(self):
|
|
||||||
+ self.assertEqual(parse_lisp("a"), "a")
|
|
||||||
+ self.assertEqual(parse_lisp("(a b c)"), ["a", "b", "c"])
|
|
||||||
+
|
|
||||||
+ def test_parse_nested_lists(self):
|
|
||||||
+ self.assertEqual(parse_lisp("(1 (2 3) 4)"), [1, [2, 3], 4])
|
|
||||||
+ self.assertEqual(parse_lisp("((a b) (c d))"), [["a", "b"], ["c", "d"]])
|
|
||||||
+
|
|
||||||
+ def test_unbalanced_parentheses(self):
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp("(")
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp(")")
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp("(1 2 (3 4)")
|
|
||||||
+
|
|
||||||
+if __name__ == "__main__":
|
|
||||||
+ unittest.main()
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
diff --git a/lisp.py b/lisp.py
|
|
||||||
index 0000000..f2a5c3d
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/lisp.py
|
|
||||||
@@ -0,0 +1,78 @@
|
|
||||||
+import re
|
|
||||||
+
|
|
||||||
+class LispParser:
|
|
||||||
+ def __init__(self, expression):
|
|
||||||
+ self.expression = expression
|
|
||||||
+ self.tokens = self.tokenize()
|
|
||||||
+ self.index = 0
|
|
||||||
+
|
|
||||||
+ def tokenize(self):
|
|
||||||
+ token_pattern = r'\s*([()])|(\d+)|([a-zA-Z_][a-zA-Z0-9_]*)'
|
|
||||||
+ tokens = re.findall(token_pattern, self.expression)
|
|
||||||
+ return [token for group in tokens for token in group if token]
|
|
||||||
+
|
|
||||||
+ def parse(self):
|
|
||||||
+ result = []
|
|
||||||
+ while self.index < len(self.tokens):
|
|
||||||
+ token = self.tokens[self.index]
|
|
||||||
+ if token == '(':
|
|
||||||
+ self.index += 1
|
|
||||||
+ result.append(self.parse())
|
|
||||||
+ elif token == ')':
|
|
||||||
+ return result
|
|
||||||
+ else:
|
|
||||||
+ result.append(self.parse_atom(token))
|
|
||||||
+ self.index += 1
|
|
||||||
+ if len(result) == 1:
|
|
||||||
+ return result[0]
|
|
||||||
+ return result
|
|
||||||
+
|
|
||||||
+ def parse_atom(self, token):
|
|
||||||
+ try:
|
|
||||||
+ return int(token)
|
|
||||||
+ except ValueError:
|
|
||||||
+ return token
|
|
||||||
+
|
|
||||||
+def parse_lisp(expression):
|
|
||||||
+ parser = LispParser(expression)
|
|
||||||
+ return parser.parse()
|
|
||||||
+
|
|
||||||
+if __name__ == "__main__":
|
|
||||||
+ expression = "(1 2 (3 4) a b)"
|
|
||||||
+ print(parse_lisp(expression))
|
|
||||||
diff --git a/tests/test_lisp.py b/tests/test_lisp.py
|
|
||||||
index 0000000..b5c7d8e
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/tests/test_lisp.py
|
|
||||||
@@ -0,0 +1,39 @@
|
|
||||||
+import unittest
|
|
||||||
+from lisp import parse_lisp
|
|
||||||
+
|
|
||||||
+class TestLispParser(unittest.TestCase):
|
|
||||||
+ def test_parse_numbers(self):
|
|
||||||
+ self.assertEqual(parse_lisp("123"), 123)
|
|
||||||
+ self.assertEqual(parse_lisp("(1 2 3)"), [1, 2, 3])
|
|
||||||
+
|
|
||||||
+ def test_parse_symbols(self):
|
|
||||||
+ self.assertEqual(parse_lisp("a"), "a")
|
|
||||||
+ self.assertEqual(parse_lisp("(a b c)"), ["a", "b", "c"])
|
|
||||||
+
|
|
||||||
+ def test_parse_nested_lists(self):
|
|
||||||
+ self.assertEqual(parse_lisp("(1 (2 3) 4)"), [1, [2, 3], 4])
|
|
||||||
+ self.assertEqual(parse_lisp("((a b) (c d))"), [["a", "b"], ["c", "d"]])
|
|
||||||
+
|
|
||||||
+ def test_unbalanced_parentheses(self):
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp("(")
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp(")")
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp("(1 2 (3 4)")
|
|
||||||
+
|
|
||||||
+if __name__ == "__main__":
|
|
||||||
+ unittest.main()
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
diff --git a/lisp.py b/lisp.py
|
|
||||||
index 0000000..f2a5c3d
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/lisp.py
|
|
||||||
@@ -0,0 +1,78 @@
|
|
||||||
+import re
|
|
||||||
+
|
|
||||||
+class LispParser:
|
|
||||||
+ def __init__(self, expression):
|
|
||||||
+ self.expression = expression
|
|
||||||
+ self.tokens = self.tokenize()
|
|
||||||
+ self.index = 0
|
|
||||||
+
|
|
||||||
+ def tokenize(self):
|
|
||||||
+ token_pattern = r'\s*([()])|(\d+)|([a-zA-Z_][a-zA-Z0-9_]*)'
|
|
||||||
+ tokens = re.findall(token_pattern, self.expression)
|
|
||||||
+ return [token for group in tokens for token in group if token]
|
|
||||||
+
|
|
||||||
+ def parse(self):
|
|
||||||
+ result = []
|
|
||||||
+ while self.index < len(self.tokens):
|
|
||||||
+ token = self.tokens[self.index]
|
|
||||||
+ if token == '(':
|
|
||||||
+ self.index += 1
|
|
||||||
+ result.append(self.parse())
|
|
||||||
+ elif token == ')':
|
|
||||||
+ return result
|
|
||||||
+ else:
|
|
||||||
+ result.append(self.parse_atom(token))
|
|
||||||
+ self.index += 1
|
|
||||||
+ if len(result) == 1:
|
|
||||||
+ return result[0]
|
|
||||||
+ return result
|
|
||||||
+
|
|
||||||
+ def parse_atom(self, token):
|
|
||||||
+ try:
|
|
||||||
+ return int(token)
|
|
||||||
+ except ValueError:
|
|
||||||
+ return token
|
|
||||||
+
|
|
||||||
+def parse_lisp(expression):
|
|
||||||
+ parser = LispParser(expression)
|
|
||||||
+ return parser.parse()
|
|
||||||
+
|
|
||||||
+if __name__ == "__main__":
|
|
||||||
+ expression = "(1 2 (3 4) a b)"
|
|
||||||
+ print(parse_lisp(expression))
|
|
||||||
diff --git a/tests/test_lisp.py b/tests/test_lisp.py
|
|
||||||
index 0000000..b5c7d8e
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/tests/test_lisp.py
|
|
||||||
@@ -0,0 +1,39 @@
|
|
||||||
+import unittest
|
|
||||||
+from lisp import parse_lisp
|
|
||||||
+
|
|
||||||
+class TestLispParser(unittest.TestCase):
|
|
||||||
+ def test_parse_numbers(self):
|
|
||||||
+ self.assertEqual(parse_lisp("123"), 123)
|
|
||||||
+ self.assertEqual(parse_lisp("(1 2 3)"), [1, 2, 3])
|
|
||||||
+
|
|
||||||
+ def test_parse_symbols(self):
|
|
||||||
+ self.assertEqual(parse_lisp("a"), "a")
|
|
||||||
+ self.assertEqual(parse_lisp("(a b c)"), ["a", "b", "c"])
|
|
||||||
+
|
|
||||||
+ def test_parse_nested_lists(self):
|
|
||||||
+ self.assertEqual(parse_lisp("(1 (2 3) 4)"), [1, [2, 3], 4])
|
|
||||||
+ self.assertEqual(parse_lisp("((a b) (c d))"), [["a", "b"], ["c", "d"]])
|
|
||||||
+
|
|
||||||
+ def test_unbalanced_parentheses(self):
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp("(")
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp(")")
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp("(1 2 (3 4)")
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
# Context Out
|
|
||||||
|
|
||||||
Task: `TASK-001`
|
|
||||||
Status: failed
|
|
||||||
Reason: Retry limit reached after stage 'apply_patch': Patch apply failed with code 128.
|
|
||||||
|
|
||||||
## Retry Notes
|
|
||||||
|
|
||||||
- Context update from 'implement': Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/implementation-summary.md
|
|
||||||
- Retry 1: stage 'validate_patch' returned fail (Patch validation failed: patch creates existing file `lisp.py`.); redirecting to 'implement'.
|
|
||||||
- Context update from 'implement': Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-summary-1.md
|
|
||||||
- Context update from 'apply_patch': C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:6: trailing whitespace.
|
|
||||||
import re
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:7: trailing whitespace.
|
|
||||||
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:8: trailing whitespace.
|
|
||||||
class LispParser:
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:9: trailing whitespace.
|
|
||||||
def __init__(self, expression):
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:10: trailing whitespace.
|
|
||||||
self.expression = expression
|
|
||||||
error: corrupt patch at line 48
|
|
||||||
- Retry 2: stage 'apply_patch' returned fail (Patch apply failed with code 128.); redirecting to 'implement'.
|
|
||||||
- Context update from 'implement': Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-summary-2.md
|
|
||||||
- Context update from 'apply_patch': C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:6: trailing whitespace.
|
|
||||||
import re
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:7: trailing whitespace.
|
|
||||||
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:8: trailing whitespace.
|
|
||||||
class LispParser:
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:9: trailing whitespace.
|
|
||||||
def __init__(self, expression):
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:10: trailing whitespace.
|
|
||||||
self.expression = expression
|
|
||||||
error: corrupt patch at line 48
|
|
||||||
- Retry 3: stage 'apply_patch' returned fail (Patch apply failed with code 128.); redirecting to 'implement'.
|
|
||||||
- Context update from 'implement': Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-summary-3.md
|
|
||||||
- Context update from 'apply_patch': C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:6: trailing whitespace.
|
|
||||||
import re
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:7: trailing whitespace.
|
|
||||||
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:8: trailing whitespace.
|
|
||||||
class LispParser:
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:9: trailing whitespace.
|
|
||||||
def __init__(self, expression):
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:10: trailing whitespace.
|
|
||||||
self.expression = expression
|
|
||||||
error: corrupt patch at line 48
|
|
||||||
|
|
||||||
## Durable Notes
|
|
||||||
|
|
||||||
- Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/implementation-summary.md
|
|
||||||
- Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-summary-1.md
|
|
||||||
- C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:6: trailing whitespace.
|
|
||||||
import re
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:7: trailing whitespace.
|
|
||||||
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:8: trailing whitespace.
|
|
||||||
class LispParser:
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:9: trailing whitespace.
|
|
||||||
def __init__(self, expression):
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:10: trailing whitespace.
|
|
||||||
self.expression = expression
|
|
||||||
error: corrupt patch at line 48
|
|
||||||
- Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-summary-2.md
|
|
||||||
- C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:6: trailing whitespace.
|
|
||||||
import re
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:7: trailing whitespace.
|
|
||||||
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:8: trailing whitespace.
|
|
||||||
class LispParser:
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:9: trailing whitespace.
|
|
||||||
def __init__(self, expression):
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:10: trailing whitespace.
|
|
||||||
self.expression = expression
|
|
||||||
error: corrupt patch at line 48
|
|
||||||
- Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-summary-3.md
|
|
||||||
- C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:6: trailing whitespace.
|
|
||||||
import re
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:7: trailing whitespace.
|
|
||||||
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:8: trailing whitespace.
|
|
||||||
class LispParser:
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:9: trailing whitespace.
|
|
||||||
def __init__(self, expression):
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:10: trailing whitespace.
|
|
||||||
self.expression = expression
|
|
||||||
error: corrupt patch at line 48
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
# Context Pack
|
|
||||||
|
|
||||||
Task: `TASK-001`
|
|
||||||
Title: Parse Lisp expressions
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
- Parses numbers
|
|
||||||
- Parses symbols
|
|
||||||
- Parses nested lists
|
|
||||||
- Raises useful errors for unbalanced parentheses
|
|
||||||
- Includes unit tests
|
|
||||||
|
|
||||||
## Constraints
|
|
||||||
|
|
||||||
- Scoped paths: .
|
|
||||||
- Repository lookups are read-only.
|
|
||||||
- Excerpts are line-numbered where files are read directly.
|
|
||||||
|
|
||||||
## Relevant Files
|
|
||||||
|
|
||||||
```text
|
|
||||||
lisp.py
|
|
||||||
tests/__init__.py
|
|
||||||
tests/test_lisp.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## Search Results
|
|
||||||
|
|
||||||
### Search: parse
|
|
||||||
|
|
||||||
```text
|
|
||||||
tasks.md:21: Evaluate parsed arithmetic expressions.
|
|
||||||
```
|
|
||||||
|
|
||||||
### Search: lisp
|
|
||||||
|
|
||||||
```text
|
|
||||||
nightshift.yaml:2: name: tiny-lisp
|
|
||||||
```
|
|
||||||
|
|
||||||
### Search: expressions
|
|
||||||
|
|
||||||
```text
|
|
||||||
tasks.md:3: - [ ] TASK-001: Parse Lisp expressions
|
|
||||||
tasks.md:21: Evaluate parsed arithmetic expressions.
|
|
||||||
tasks.md:47: Implement simple truthiness and `if` expressions.
|
|
||||||
```
|
|
||||||
|
|
||||||
### Search: parses
|
|
||||||
|
|
||||||
```text
|
|
||||||
No matches found.
|
|
||||||
```
|
|
||||||
|
|
||||||
### Search: numbers
|
|
||||||
|
|
||||||
```text
|
|
||||||
tasks.md:9: - Parses numbers
|
|
||||||
```
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
# Task Context
|
|
||||||
|
|
||||||
Task: `TASK-001`
|
|
||||||
Title: Parse Lisp expressions
|
|
||||||
|
|
||||||
## Description
|
|
||||||
|
|
||||||
Implement tokenization and parsing for a tiny Lisp subset.
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
- Parses numbers
|
|
||||||
- Parses symbols
|
|
||||||
- Parses nested lists
|
|
||||||
- Raises useful errors for unbalanced parentheses
|
|
||||||
- Includes unit tests
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
Git diff unavailable.
|
|
||||||
|
|
||||||
Reason: project root is not a git work tree.
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
# Repo Tool Calls
|
|
||||||
|
|
||||||
## 1. read_file
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
- path: `lisp_parser.py`
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
```text
|
|
||||||
File not found: lisp_parser.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2. read_file
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
- path: `tests/test_lisp_parser.py`
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
```text
|
|
||||||
File not found: tests/test_lisp_parser.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## 3. list_files
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
- path: `.`
|
|
||||||
- pattern: `*.py`
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
```text
|
|
||||||
lisp.py
|
|
||||||
tests/__init__.py
|
|
||||||
tests/test_lisp.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4. grep
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
- path: `.`
|
|
||||||
- pattern: `def parse\(`
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
```text
|
|
||||||
No matches found.
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5. grep
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
- path: `.`
|
|
||||||
- pattern: `class LispParser`
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
```text
|
|
||||||
No matches found.
|
|
||||||
```
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
# Final Task Notes
|
|
||||||
|
|
||||||
Task: `TASK-001`
|
|
||||||
Title: Parse Lisp expressions
|
|
||||||
Status: failed
|
|
||||||
Retry count: 3
|
|
||||||
Reason: Retry limit reached after stage 'apply_patch': Patch apply failed with code 128.
|
|
||||||
|
|
||||||
## Experiment
|
|
||||||
|
|
||||||
- Label:
|
|
||||||
- Prompt variant:
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
- Parses numbers
|
|
||||||
- Parses symbols
|
|
||||||
- Parses nested lists
|
|
||||||
- Raises useful errors for unbalanced parentheses
|
|
||||||
- Includes unit tests
|
|
||||||
|
|
||||||
## Stage Results
|
|
||||||
|
|
||||||
- `plan`: pass (Agent completed after repo lookup.)
|
|
||||||
- `context`: pass (Context pack written.)
|
|
||||||
- `implement`: pass (Proposed patch written.)
|
|
||||||
- `normalize`: pass (Normalized patch written.)
|
|
||||||
- `validate_patch`: fail (Patch validation failed: patch creates existing file `lisp.py`.)
|
|
||||||
- `implement`: pass (Proposed patch written.)
|
|
||||||
- `normalize`: pass (Normalized patch written.)
|
|
||||||
- `validate_patch`: pass (Patch validation passed.)
|
|
||||||
- `apply_patch`: fail (Patch apply failed with code 128.)
|
|
||||||
- `implement`: pass (Proposed patch written.)
|
|
||||||
- `normalize`: pass (Normalized patch written.)
|
|
||||||
- `validate_patch`: pass (Patch validation passed.)
|
|
||||||
- `apply_patch`: fail (Patch apply failed with code 128.)
|
|
||||||
- `implement`: pass (Proposed patch written.)
|
|
||||||
- `normalize`: pass (Normalized patch written.)
|
|
||||||
- `validate_patch`: pass (Patch validation passed.)
|
|
||||||
- `apply_patch`: fail (Patch apply failed with code 128.)
|
|
||||||
|
|
||||||
## Modified Files
|
|
||||||
|
|
||||||
- Unavailable or none detected
|
|
||||||
|
|
||||||
## Artifacts
|
|
||||||
|
|
||||||
- Stage results: `stage-results.md`
|
|
||||||
- Context out: `context-out.md`
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
# Git Status after-patch-apply
|
|
||||||
|
|
||||||
Available: false
|
|
||||||
Exit code: 128
|
|
||||||
|
|
||||||
## stdout
|
|
||||||
|
|
||||||
```text
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## stderr
|
|
||||||
|
|
||||||
```text
|
|
||||||
fatal: not a git repository (or any of the parent directories): .git
|
|
||||||
```
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
# Git Status after
|
|
||||||
|
|
||||||
Available: false
|
|
||||||
Exit code: 128
|
|
||||||
|
|
||||||
## stdout
|
|
||||||
|
|
||||||
```text
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## stderr
|
|
||||||
|
|
||||||
```text
|
|
||||||
fatal: not a git repository (or any of the parent directories): .git
|
|
||||||
```
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
# Git Status before-patch-apply
|
|
||||||
|
|
||||||
Available: false
|
|
||||||
Exit code: 128
|
|
||||||
|
|
||||||
## stdout
|
|
||||||
|
|
||||||
```text
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## stderr
|
|
||||||
|
|
||||||
```text
|
|
||||||
fatal: not a git repository (or any of the parent directories): .git
|
|
||||||
```
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
# Git Status before
|
|
||||||
|
|
||||||
Available: false
|
|
||||||
Exit code: 128
|
|
||||||
|
|
||||||
## stdout
|
|
||||||
|
|
||||||
```text
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## stderr
|
|
||||||
|
|
||||||
```text
|
|
||||||
fatal: not a git repository (or any of the parent directories): .git
|
|
||||||
```
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
# Implementation Summary
|
|
||||||
|
|
||||||
Stage: `implement`
|
|
||||||
Status: pass
|
|
||||||
Repair attempt: 0
|
|
||||||
Patch: `.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/proposed.patch`
|
|
||||||
|
|
||||||
## Retry Feedback
|
|
||||||
|
|
||||||
- None
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
diff --git a/lisp.py b/lisp.py
|
|
||||||
index 0000000..f2a5c3d
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/lisp.py
|
|
||||||
@@ -0,0 +1,78 @@
|
|
||||||
+import re
|
|
||||||
+
|
|
||||||
+class LispParser:
|
|
||||||
+ def __init__(self, expression):
|
|
||||||
+ self.expression = expression
|
|
||||||
+ self.tokens = self.tokenize()
|
|
||||||
+ self.index = 0
|
|
||||||
+
|
|
||||||
+ def tokenize(self):
|
|
||||||
+ token_pattern = r'\s*([()])|(\d+)|([a-zA-Z_][a-zA-Z0-9_]*)'
|
|
||||||
+ tokens = re.findall(token_pattern, self.expression)
|
|
||||||
+ return [token for group in tokens for token in group if token]
|
|
||||||
+
|
|
||||||
+ def parse(self):
|
|
||||||
+ result = []
|
|
||||||
+ while self.index < len(self.tokens):
|
|
||||||
+ token = self.tokens[self.index]
|
|
||||||
+ if token == '(':
|
|
||||||
+ self.index += 1
|
|
||||||
+ result.append(self.parse())
|
|
||||||
+ elif token == ')':
|
|
||||||
+ return result
|
|
||||||
+ else:
|
|
||||||
+ result.append(self.parse_atom(token))
|
|
||||||
+ self.index += 1
|
|
||||||
+ if len(result) == 1:
|
|
||||||
+ return result[0]
|
|
||||||
+ return result
|
|
||||||
+
|
|
||||||
+ def parse_atom(self, token):
|
|
||||||
+ try:
|
|
||||||
+ return int(token)
|
|
||||||
+ except ValueError:
|
|
||||||
+ return token
|
|
||||||
+
|
|
||||||
+def parse_lisp(expression):
|
|
||||||
+ parser = LispParser(expression)
|
|
||||||
+ return parser.parse()
|
|
||||||
+
|
|
||||||
+if __name__ == "__main__":
|
|
||||||
+ expression = "(1 2 (3 4) a b)"
|
|
||||||
+ print(parse_lisp(expression))
|
|
||||||
diff --git a/tests/test_lisp.py b/tests/test_lisp.py
|
|
||||||
index 0000000..b5c7d8e
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/tests/test_lisp.py
|
|
||||||
@@ -0,0 +1,39 @@
|
|
||||||
+import unittest
|
|
||||||
+from lisp import parse_lisp
|
|
||||||
+
|
|
||||||
+class TestLispParser(unittest.TestCase):
|
|
||||||
+ def test_parse_numbers(self):
|
|
||||||
+ self.assertEqual(parse_lisp("123"), 123)
|
|
||||||
+ self.assertEqual(parse_lisp("(1 2 3)"), [1, 2, 3])
|
|
||||||
+
|
|
||||||
+ def test_parse_symbols(self):
|
|
||||||
+ self.assertEqual(parse_lisp("a"), "a")
|
|
||||||
+ self.assertEqual(parse_lisp("(a b c)"), ["a", "b", "c"])
|
|
||||||
+
|
|
||||||
+ def test_parse_nested_lists(self):
|
|
||||||
+ self.assertEqual(parse_lisp("(1 (2 3) 4)"), [1, [2, 3], 4])
|
|
||||||
+ self.assertEqual(parse_lisp("((a b) (c d))"), [["a", "b"], ["c", "d"]])
|
|
||||||
+
|
|
||||||
+ def test_unbalanced_parentheses(self):
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp("(")
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp(")")
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp("(1 2 (3 4)")
|
|
||||||
+
|
|
||||||
+if __name__ == "__main__":
|
|
||||||
+ unittest.main()
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
diff --git a/lisp.py b/lisp.py
|
|
||||||
index 0000000..f2a5c3d
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/lisp.py
|
|
||||||
@@ -0,0 +1,78 @@
|
|
||||||
+import re
|
|
||||||
+
|
|
||||||
+class LispParser:
|
|
||||||
+ def __init__(self, expression):
|
|
||||||
+ self.expression = expression
|
|
||||||
+ self.tokens = self.tokenize()
|
|
||||||
+ self.index = 0
|
|
||||||
+
|
|
||||||
+ def tokenize(self):
|
|
||||||
+ token_pattern = r'\s*([()])|(\d+)|([a-zA-Z_][a-zA-Z0-9_]*)'
|
|
||||||
+ tokens = re.findall(token_pattern, self.expression)
|
|
||||||
+ return [token for group in tokens for token in group if token]
|
|
||||||
+
|
|
||||||
+ def parse(self):
|
|
||||||
+ result = []
|
|
||||||
+ while self.index < len(self.tokens):
|
|
||||||
+ token = self.tokens[self.index]
|
|
||||||
+ if token == '(':
|
|
||||||
+ self.index += 1
|
|
||||||
+ result.append(self.parse())
|
|
||||||
+ elif token == ')':
|
|
||||||
+ return result
|
|
||||||
+ else:
|
|
||||||
+ result.append(self.parse_atom(token))
|
|
||||||
+ self.index += 1
|
|
||||||
+ if len(result) == 1:
|
|
||||||
+ return result[0]
|
|
||||||
+ return result
|
|
||||||
+
|
|
||||||
+ def parse_atom(self, token):
|
|
||||||
+ try:
|
|
||||||
+ return int(token)
|
|
||||||
+ except ValueError:
|
|
||||||
+ return token
|
|
||||||
+
|
|
||||||
+def parse_lisp(expression):
|
|
||||||
+ parser = LispParser(expression)
|
|
||||||
+ return parser.parse()
|
|
||||||
+
|
|
||||||
+if __name__ == "__main__":
|
|
||||||
+ expression = "(1 2 (3 4) a b)"
|
|
||||||
+ print(parse_lisp(expression))
|
|
||||||
diff --git a/tests/test_lisp.py b/tests/test_lisp.py
|
|
||||||
index 0000000..b5c7d8e
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/tests/test_lisp.py
|
|
||||||
@@ -0,0 +1,39 @@
|
|
||||||
+import unittest
|
|
||||||
+from lisp import parse_lisp
|
|
||||||
+
|
|
||||||
+class TestLispParser(unittest.TestCase):
|
|
||||||
+ def test_parse_numbers(self):
|
|
||||||
+ self.assertEqual(parse_lisp("123"), 123)
|
|
||||||
+ self.assertEqual(parse_lisp("(1 2 3)"), [1, 2, 3])
|
|
||||||
+
|
|
||||||
+ def test_parse_symbols(self):
|
|
||||||
+ self.assertEqual(parse_lisp("a"), "a")
|
|
||||||
+ self.assertEqual(parse_lisp("(a b c)"), ["a", "b", "c"])
|
|
||||||
+
|
|
||||||
+ def test_parse_nested_lists(self):
|
|
||||||
+ self.assertEqual(parse_lisp("(1 (2 3) 4)"), [1, [2, 3], 4])
|
|
||||||
+ self.assertEqual(parse_lisp("((a b) (c d))"), [["a", "b"], ["c", "d"]])
|
|
||||||
+
|
|
||||||
+ def test_unbalanced_parentheses(self):
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp("(")
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp(")")
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp("(1 2 (3 4)")
|
|
||||||
+
|
|
||||||
+if __name__ == "__main__":
|
|
||||||
+ unittest.main()
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
diff --git a/lisp.py b/lisp.py
|
|
||||||
index 0000000..f2a5c3d
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/lisp.py
|
|
||||||
@@ -0,0 +1,78 @@
|
|
||||||
+import re
|
|
||||||
+
|
|
||||||
+class LispParser:
|
|
||||||
+ def __init__(self, expression):
|
|
||||||
+ self.expression = expression
|
|
||||||
+ self.tokens = self.tokenize()
|
|
||||||
+ self.index = 0
|
|
||||||
+
|
|
||||||
+ def tokenize(self):
|
|
||||||
+ token_pattern = r'\s*([()])|(\d+)|([a-zA-Z_][a-zA-Z0-9_]*)'
|
|
||||||
+ tokens = re.findall(token_pattern, self.expression)
|
|
||||||
+ return [token for group in tokens for token in group if token]
|
|
||||||
+
|
|
||||||
+ def parse(self):
|
|
||||||
+ result = []
|
|
||||||
+ while self.index < len(self.tokens):
|
|
||||||
+ token = self.tokens[self.index]
|
|
||||||
+ if token == '(':
|
|
||||||
+ self.index += 1
|
|
||||||
+ result.append(self.parse())
|
|
||||||
+ elif token == ')':
|
|
||||||
+ return result
|
|
||||||
+ else:
|
|
||||||
+ result.append(self.parse_atom(token))
|
|
||||||
+ self.index += 1
|
|
||||||
+ if len(result) == 1:
|
|
||||||
+ return result[0]
|
|
||||||
+ return result
|
|
||||||
+
|
|
||||||
+ def parse_atom(self, token):
|
|
||||||
+ try:
|
|
||||||
+ return int(token)
|
|
||||||
+ except ValueError:
|
|
||||||
+ return token
|
|
||||||
+
|
|
||||||
+def parse_lisp(expression):
|
|
||||||
+ parser = LispParser(expression)
|
|
||||||
+ return parser.parse()
|
|
||||||
+
|
|
||||||
+if __name__ == "__main__":
|
|
||||||
+ expression = "(1 2 (3 4) a b)"
|
|
||||||
+ print(parse_lisp(expression))
|
|
||||||
diff --git a/tests/test_lisp.py b/tests/test_lisp.py
|
|
||||||
index 0000000..b5c7d8e
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/tests/test_lisp.py
|
|
||||||
@@ -0,0 +1,39 @@
|
|
||||||
+import unittest
|
|
||||||
+from lisp import parse_lisp
|
|
||||||
+
|
|
||||||
+class TestLispParser(unittest.TestCase):
|
|
||||||
+ def test_parse_numbers(self):
|
|
||||||
+ self.assertEqual(parse_lisp("123"), 123)
|
|
||||||
+ self.assertEqual(parse_lisp("(1 2 3)"), [1, 2, 3])
|
|
||||||
+
|
|
||||||
+ def test_parse_symbols(self):
|
|
||||||
+ self.assertEqual(parse_lisp("a"), "a")
|
|
||||||
+ self.assertEqual(parse_lisp("(a b c)"), ["a", "b", "c"])
|
|
||||||
+
|
|
||||||
+ def test_parse_nested_lists(self):
|
|
||||||
+ self.assertEqual(parse_lisp("(1 (2 3) 4)"), [1, [2, 3], 4])
|
|
||||||
+ self.assertEqual(parse_lisp("((a b) (c d))"), [["a", "b"], ["c", "d"]])
|
|
||||||
+
|
|
||||||
+ def test_unbalanced_parentheses(self):
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp("(")
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp(")")
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp("(1 2 (3 4)")
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
diff --git a/lisp.py b/lisp.py
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000..f2a5c3d
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/lisp.py
|
|
||||||
@@ -0,0 +1,78 @@
|
|
||||||
+import re
|
|
||||||
+
|
|
||||||
+class LispParser:
|
|
||||||
+ def __init__(self, expression):
|
|
||||||
+ self.expression = expression
|
|
||||||
+ self.tokens = self.tokenize()
|
|
||||||
+ self.index = 0
|
|
||||||
+
|
|
||||||
+ def tokenize(self):
|
|
||||||
+ token_pattern = r'\s*([()])|(\d+)|([a-zA-Z_][a-zA-Z0-9_]*)'
|
|
||||||
+ tokens = re.findall(token_pattern, self.expression)
|
|
||||||
+ return [token for group in tokens for token in group if token]
|
|
||||||
+
|
|
||||||
+ def parse(self):
|
|
||||||
+ result = []
|
|
||||||
+ while self.index < len(self.tokens):
|
|
||||||
+ token = self.tokens[self.index]
|
|
||||||
+ if token == '(':
|
|
||||||
+ self.index += 1
|
|
||||||
+ result.append(self.parse())
|
|
||||||
+ elif token == ')':
|
|
||||||
+ return result
|
|
||||||
+ else:
|
|
||||||
+ result.append(self.parse_atom(token))
|
|
||||||
+ self.index += 1
|
|
||||||
+ if len(result) == 1:
|
|
||||||
+ return result[0]
|
|
||||||
+ return result
|
|
||||||
+
|
|
||||||
+ def parse_atom(self, token):
|
|
||||||
+ try:
|
|
||||||
+ return int(token)
|
|
||||||
+ except ValueError:
|
|
||||||
+ return token
|
|
||||||
+
|
|
||||||
+def parse_lisp(expression):
|
|
||||||
+ parser = LispParser(expression)
|
|
||||||
+ return parser.parse()
|
|
||||||
+
|
|
||||||
+if __name__ == "__main__":
|
|
||||||
+ expression = "(1 2 (3 4) a b)"
|
|
||||||
+ print(parse_lisp(expression))
|
|
||||||
diff --git a/tests/test_lisp.py b/tests/test_lisp.py
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000..b5c7d8e
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/tests/test_lisp.py
|
|
||||||
@@ -0,0 +1,39 @@
|
|
||||||
+import unittest
|
|
||||||
+from lisp import parse_lisp
|
|
||||||
+
|
|
||||||
+class TestLispParser(unittest.TestCase):
|
|
||||||
+ def test_parse_numbers(self):
|
|
||||||
+ self.assertEqual(parse_lisp("123"), 123)
|
|
||||||
+ self.assertEqual(parse_lisp("(1 2 3)"), [1, 2, 3])
|
|
||||||
+
|
|
||||||
+ def test_parse_symbols(self):
|
|
||||||
+ self.assertEqual(parse_lisp("a"), "a")
|
|
||||||
+ self.assertEqual(parse_lisp("(a b c)"), ["a", "b", "c"])
|
|
||||||
+
|
|
||||||
+ def test_parse_nested_lists(self):
|
|
||||||
+ self.assertEqual(parse_lisp("(1 (2 3) 4)"), [1, [2, 3], 4])
|
|
||||||
+ self.assertEqual(parse_lisp("((a b) (c d))"), [["a", "b"], ["c", "d"]])
|
|
||||||
+
|
|
||||||
+ def test_unbalanced_parentheses(self):
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp("(")
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp(")")
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp("(1 2 (3 4)")
|
|
||||||
+
|
|
||||||
+if __name__ == "__main__":
|
|
||||||
+ unittest.main()
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
# Patch Apply
|
|
||||||
|
|
||||||
Status: fail
|
|
||||||
Mode: apply
|
|
||||||
Patch: `.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch`
|
|
||||||
Command: `git apply --ignore-whitespace C:\Users\metis\Documents\tiny-lisp-nightshift\.nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\applied-1.patch`
|
|
||||||
Exit code: 128
|
|
||||||
|
|
||||||
## stdout
|
|
||||||
|
|
||||||
```text
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## stderr
|
|
||||||
|
|
||||||
```text
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:6: trailing whitespace.
|
|
||||||
import re
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:7: trailing whitespace.
|
|
||||||
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:8: trailing whitespace.
|
|
||||||
class LispParser:
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:9: trailing whitespace.
|
|
||||||
def __init__(self, expression):
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:10: trailing whitespace.
|
|
||||||
self.expression = expression
|
|
||||||
error: corrupt patch at line 48
|
|
||||||
```
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
# Patch Apply
|
|
||||||
|
|
||||||
Status: fail
|
|
||||||
Mode: apply
|
|
||||||
Patch: `.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch`
|
|
||||||
Command: `git apply --ignore-whitespace C:\Users\metis\Documents\tiny-lisp-nightshift\.nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\applied-2.patch`
|
|
||||||
Exit code: 128
|
|
||||||
|
|
||||||
## stdout
|
|
||||||
|
|
||||||
```text
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## stderr
|
|
||||||
|
|
||||||
```text
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:6: trailing whitespace.
|
|
||||||
import re
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:7: trailing whitespace.
|
|
||||||
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:8: trailing whitespace.
|
|
||||||
class LispParser:
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:9: trailing whitespace.
|
|
||||||
def __init__(self, expression):
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:10: trailing whitespace.
|
|
||||||
self.expression = expression
|
|
||||||
error: corrupt patch at line 48
|
|
||||||
```
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
# Patch Apply
|
|
||||||
|
|
||||||
Status: fail
|
|
||||||
Mode: apply
|
|
||||||
Patch: `.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch`
|
|
||||||
Command: `git apply --ignore-whitespace C:\Users\metis\Documents\tiny-lisp-nightshift\.nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\applied-3.patch`
|
|
||||||
Exit code: 128
|
|
||||||
|
|
||||||
## stdout
|
|
||||||
|
|
||||||
```text
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## stderr
|
|
||||||
|
|
||||||
```text
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:6: trailing whitespace.
|
|
||||||
import re
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:7: trailing whitespace.
|
|
||||||
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:8: trailing whitespace.
|
|
||||||
class LispParser:
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:9: trailing whitespace.
|
|
||||||
def __init__(self, expression):
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:10: trailing whitespace.
|
|
||||||
self.expression = expression
|
|
||||||
error: corrupt patch at line 48
|
|
||||||
```
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
# Patch Validation
|
|
||||||
|
|
||||||
Status: pass
|
|
||||||
Changed files: 2
|
|
||||||
Changed lines: 68
|
|
||||||
|
|
||||||
## Files
|
|
||||||
|
|
||||||
- `lisp.py`
|
|
||||||
- `tests/test_lisp.py`
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
# Patch Validation
|
|
||||||
|
|
||||||
Status: pass
|
|
||||||
Changed files: 2
|
|
||||||
Changed lines: 68
|
|
||||||
|
|
||||||
## Files
|
|
||||||
|
|
||||||
- `lisp.py`
|
|
||||||
- `tests/test_lisp.py`
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
# Patch Validation
|
|
||||||
|
|
||||||
Status: pass
|
|
||||||
Changed files: 2
|
|
||||||
Changed lines: 65
|
|
||||||
|
|
||||||
## Files
|
|
||||||
|
|
||||||
- `lisp.py`
|
|
||||||
- `tests/test_lisp.py`
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
# Patch Validation
|
|
||||||
|
|
||||||
Status: fail
|
|
||||||
Reason: Patch validation failed: patch creates existing file `lisp.py`.
|
|
||||||
|
|
@ -1,193 +0,0 @@
|
||||||
# Agent Output: plan
|
|
||||||
|
|
||||||
Agent: `planner`
|
|
||||||
Command: `POST http://localhost:11434/api/generate`
|
|
||||||
Exit code: 0
|
|
||||||
Duration seconds: 0.942
|
|
||||||
Timed out: false
|
|
||||||
|
|
||||||
## stdout
|
|
||||||
|
|
||||||
```text
|
|
||||||
lookup_requests:
|
|
||||||
- tool: read_file
|
|
||||||
path: lisp.py
|
|
||||||
- tool: read_file
|
|
||||||
path: tests/test_lisp.py
|
|
||||||
|
|
||||||
After context is provided, write a short plan with:
|
|
||||||
- files to edit
|
|
||||||
- tests to add or update
|
|
||||||
- risks
|
|
||||||
```
|
|
||||||
|
|
||||||
## stderr
|
|
||||||
|
|
||||||
```text
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## Prompt
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# NightShift Agent Input
|
|
||||||
|
|
||||||
## System Prompt
|
|
||||||
|
|
||||||
You are the planning agent for NightShift.
|
|
||||||
|
|
||||||
Create a concise implementation plan for the current task.
|
|
||||||
|
|
||||||
If you need repository context before planning, output lookup requests exactly like this:
|
|
||||||
|
|
||||||
lookup_requests:
|
|
||||||
- tool: read_file
|
|
||||||
path: relative/path.py
|
|
||||||
- tool: grep
|
|
||||||
path: .
|
|
||||||
pattern: search_regex
|
|
||||||
|
|
||||||
After context is provided, write a short plan with:
|
|
||||||
- files to edit
|
|
||||||
- tests to add or update
|
|
||||||
- risks
|
|
||||||
|
|
||||||
Do not write code.
|
|
||||||
|
|
||||||
## Stage
|
|
||||||
|
|
||||||
- id: plan
|
|
||||||
- type: agent
|
|
||||||
|
|
||||||
## Task
|
|
||||||
|
|
||||||
- [ ] TASK-001: Parse Lisp expressions
|
|
||||||
|
|
||||||
Description:
|
|
||||||
Implement tokenization and parsing for a tiny Lisp subset.
|
|
||||||
|
|
||||||
Acceptance Criteria:
|
|
||||||
- Parses numbers
|
|
||||||
- Parses symbols
|
|
||||||
- Parses nested lists
|
|
||||||
- Raises useful errors for unbalanced parentheses
|
|
||||||
- Includes unit tests
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
- Parses numbers
|
|
||||||
- Parses symbols
|
|
||||||
- Parses nested lists
|
|
||||||
- Raises useful errors for unbalanced parentheses
|
|
||||||
- Includes unit tests
|
|
||||||
|
|
||||||
## Project Context
|
|
||||||
|
|
||||||
# Project Context
|
|
||||||
|
|
||||||
## Task Context
|
|
||||||
|
|
||||||
# Task Context
|
|
||||||
|
|
||||||
Task: `TASK-001`
|
|
||||||
Title: Parse Lisp expressions
|
|
||||||
|
|
||||||
## Description
|
|
||||||
|
|
||||||
Implement tokenization and parsing for a tiny Lisp subset.
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
- Parses numbers
|
|
||||||
- Parses symbols
|
|
||||||
- Parses nested lists
|
|
||||||
- Raises useful errors for unbalanced parentheses
|
|
||||||
- Includes unit tests
|
|
||||||
|
|
||||||
## Previous Stage Output
|
|
||||||
|
|
||||||
## repo_lookup_results
|
|
||||||
|
|
||||||
# Repo Tool Calls
|
|
||||||
|
|
||||||
## 1. read_file
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
- path: `lisp_parser.py`
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
```text
|
|
||||||
File not found: lisp_parser.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2. read_file
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
- path: `tests/test_lisp_parser.py`
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
```text
|
|
||||||
File not found: tests/test_lisp_parser.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## 3. list_files
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
- path: `.`
|
|
||||||
- pattern: `*.py`
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
```text
|
|
||||||
lisp.py
|
|
||||||
tests/__init__.py
|
|
||||||
tests/test_lisp.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4. grep
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
- path: `.`
|
|
||||||
- pattern: `def parse\(`
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
```text
|
|
||||||
No matches found.
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5. grep
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
- path: `.`
|
|
||||||
- pattern: `class LispParser`
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
```text
|
|
||||||
No matches found.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Retry Notes
|
|
||||||
|
|
||||||
- Repository lookup results have been provided. Write the final plan now; do not request more lookups.
|
|
||||||
|
|
||||||
## Output Contract
|
|
||||||
|
|
||||||
Write the requested stage output in concise markdown.
|
|
||||||
|
|
||||||
If you need repository context before finalizing the plan, include:
|
|
||||||
lookup_requests:
|
|
||||||
- tool: list_files | read_file | grep
|
|
||||||
path: <relative path>
|
|
||||||
pattern: <glob for list_files or regex for grep>
|
|
||||||
|
|
||||||
Use at most 5 lookup requests.
|
|
||||||
Do not repeat the same lookup request.
|
|
||||||
Prefer read_file for likely-relevant files over many grep variations.
|
|
||||||
Do not search .nightshift, .git, virtualenvs, caches, or artifact directories.
|
|
||||||
|
|
||||||
NightShift will run these read-only lookup tools, save files-inspected.md, and re-run this planner stage with the retrieved context.
|
|
||||||
```
|
|
||||||
|
|
@ -1,825 +0,0 @@
|
||||||
# Agent Output: implement
|
|
||||||
|
|
||||||
Agent: `implementer`
|
|
||||||
Command: `POST http://localhost:11434/api/generate`
|
|
||||||
Exit code: 0
|
|
||||||
Duration seconds: 10.410
|
|
||||||
Timed out: false
|
|
||||||
|
|
||||||
## stdout
|
|
||||||
|
|
||||||
```text
|
|
||||||
diff --git a/lisp.py b/lisp.py
|
|
||||||
index 0000000..f2a5c3d
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/lisp.py
|
|
||||||
@@ -0,0 +1,78 @@
|
|
||||||
+import re
|
|
||||||
+
|
|
||||||
+class LispParser:
|
|
||||||
+ def __init__(self, expression):
|
|
||||||
+ self.expression = expression
|
|
||||||
+ self.tokens = self.tokenize()
|
|
||||||
+ self.index = 0
|
|
||||||
+
|
|
||||||
+ def tokenize(self):
|
|
||||||
+ token_pattern = r'\s*([()])|(\d+)|([a-zA-Z_][a-zA-Z0-9_]*)'
|
|
||||||
+ tokens = re.findall(token_pattern, self.expression)
|
|
||||||
+ return [token for group in tokens for token in group if token]
|
|
||||||
+
|
|
||||||
+ def parse(self):
|
|
||||||
+ result = []
|
|
||||||
+ while self.index < len(self.tokens):
|
|
||||||
+ token = self.tokens[self.index]
|
|
||||||
+ if token == '(':
|
|
||||||
+ self.index += 1
|
|
||||||
+ result.append(self.parse())
|
|
||||||
+ elif token == ')':
|
|
||||||
+ return result
|
|
||||||
+ else:
|
|
||||||
+ result.append(self.parse_atom(token))
|
|
||||||
+ self.index += 1
|
|
||||||
+ if len(result) == 1:
|
|
||||||
+ return result[0]
|
|
||||||
+ return result
|
|
||||||
+
|
|
||||||
+ def parse_atom(self, token):
|
|
||||||
+ try:
|
|
||||||
+ return int(token)
|
|
||||||
+ except ValueError:
|
|
||||||
+ return token
|
|
||||||
+
|
|
||||||
+def parse_lisp(expression):
|
|
||||||
+ parser = LispParser(expression)
|
|
||||||
+ return parser.parse()
|
|
||||||
+
|
|
||||||
+if __name__ == "__main__":
|
|
||||||
+ expression = "(1 2 (3 4) a b)"
|
|
||||||
+ print(parse_lisp(expression))
|
|
||||||
diff --git a/tests/test_lisp.py b/tests/test_lisp.py
|
|
||||||
index 0000000..b5c7d8e
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/tests/test_lisp.py
|
|
||||||
@@ -0,0 +1,39 @@
|
|
||||||
+import unittest
|
|
||||||
+from lisp import parse_lisp
|
|
||||||
+
|
|
||||||
+class TestLispParser(unittest.TestCase):
|
|
||||||
+ def test_parse_numbers(self):
|
|
||||||
+ self.assertEqual(parse_lisp("123"), 123)
|
|
||||||
+ self.assertEqual(parse_lisp("(1 2 3)"), [1, 2, 3])
|
|
||||||
+
|
|
||||||
+ def test_parse_symbols(self):
|
|
||||||
+ self.assertEqual(parse_lisp("a"), "a")
|
|
||||||
+ self.assertEqual(parse_lisp("(a b c)"), ["a", "b", "c"])
|
|
||||||
+
|
|
||||||
+ def test_parse_nested_lists(self):
|
|
||||||
+ self.assertEqual(parse_lisp("(1 (2 3) 4)"), [1, [2, 3], 4])
|
|
||||||
+ self.assertEqual(parse_lisp("((a b) (c d))"), [["a", "b"], ["c", "d"]])
|
|
||||||
+
|
|
||||||
+ def test_unbalanced_parentheses(self):
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp("(")
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp(")")
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp("(1 2 (3 4)")
|
|
||||||
```
|
|
||||||
|
|
||||||
## stderr
|
|
||||||
|
|
||||||
```text
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## Prompt
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# NightShift Agent Input
|
|
||||||
|
|
||||||
## System Prompt
|
|
||||||
|
|
||||||
You are the implementation agent for NightShift.
|
|
||||||
|
|
||||||
Output only a unified diff.
|
|
||||||
Do not wrap the patch in markdown fences.
|
|
||||||
Do not include explanations before or after the patch.
|
|
||||||
Use diff --git headers.
|
|
||||||
Include tests when needed.
|
|
||||||
Keep the change as small as possible.
|
|
||||||
Only edit files needed for the task.
|
|
||||||
|
|
||||||
## Stage
|
|
||||||
|
|
||||||
- id: implement
|
|
||||||
- type: code_writer
|
|
||||||
|
|
||||||
## Task
|
|
||||||
|
|
||||||
- [ ] TASK-001: Parse Lisp expressions
|
|
||||||
|
|
||||||
Description:
|
|
||||||
Implement tokenization and parsing for a tiny Lisp subset.
|
|
||||||
|
|
||||||
Acceptance Criteria:
|
|
||||||
- Parses numbers
|
|
||||||
- Parses symbols
|
|
||||||
- Parses nested lists
|
|
||||||
- Raises useful errors for unbalanced parentheses
|
|
||||||
- Includes unit tests
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
- Parses numbers
|
|
||||||
- Parses symbols
|
|
||||||
- Parses nested lists
|
|
||||||
- Raises useful errors for unbalanced parentheses
|
|
||||||
- Includes unit tests
|
|
||||||
|
|
||||||
## Project Context
|
|
||||||
|
|
||||||
# Project Context
|
|
||||||
|
|
||||||
## Task Context
|
|
||||||
|
|
||||||
# Task Context
|
|
||||||
|
|
||||||
Task: `TASK-001`
|
|
||||||
Title: Parse Lisp expressions
|
|
||||||
|
|
||||||
## Description
|
|
||||||
|
|
||||||
Implement tokenization and parsing for a tiny Lisp subset.
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
- Parses numbers
|
|
||||||
- Parses symbols
|
|
||||||
- Parses nested lists
|
|
||||||
- Raises useful errors for unbalanced parentheses
|
|
||||||
- Includes unit tests
|
|
||||||
|
|
||||||
## Previous Stage Output
|
|
||||||
|
|
||||||
## plan
|
|
||||||
|
|
||||||
# Agent Output: plan
|
|
||||||
|
|
||||||
Agent: `planner`
|
|
||||||
Command: `POST http://localhost:11434/api/generate`
|
|
||||||
Exit code: 0
|
|
||||||
Duration seconds: 0.942
|
|
||||||
Timed out: false
|
|
||||||
|
|
||||||
## stdout
|
|
||||||
|
|
||||||
```text
|
|
||||||
lookup_requests:
|
|
||||||
- tool: read_file
|
|
||||||
path: lisp.py
|
|
||||||
- tool: read_file
|
|
||||||
path: tests/test_lisp.py
|
|
||||||
|
|
||||||
After context is provided, write a short plan with:
|
|
||||||
- files to edit
|
|
||||||
- tests to add or update
|
|
||||||
- risks
|
|
||||||
```
|
|
||||||
|
|
||||||
## stderr
|
|
||||||
|
|
||||||
```text
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## Prompt
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# NightShift Agent Input
|
|
||||||
|
|
||||||
## System Prompt
|
|
||||||
|
|
||||||
You are the planning agent for NightShift.
|
|
||||||
|
|
||||||
Create a concise implementation plan for the current task.
|
|
||||||
|
|
||||||
If you need repository context before planning, output lookup requests exactly like this:
|
|
||||||
|
|
||||||
lookup_requests:
|
|
||||||
- tool: read_file
|
|
||||||
path: relative/path.py
|
|
||||||
- tool: grep
|
|
||||||
path: .
|
|
||||||
pattern: search_regex
|
|
||||||
|
|
||||||
After context is provided, write a short plan with:
|
|
||||||
- files to edit
|
|
||||||
- tests to add or update
|
|
||||||
- risks
|
|
||||||
|
|
||||||
Do not write code.
|
|
||||||
|
|
||||||
## Stage
|
|
||||||
|
|
||||||
- id: plan
|
|
||||||
- type: agent
|
|
||||||
|
|
||||||
## Task
|
|
||||||
|
|
||||||
- [ ] TASK-001: Parse Lisp expressions
|
|
||||||
|
|
||||||
Description:
|
|
||||||
Implement tokenization and parsing for a tiny Lisp subset.
|
|
||||||
|
|
||||||
Acceptance Criteria:
|
|
||||||
- Parses numbers
|
|
||||||
- Parses symbols
|
|
||||||
- Parses nested lists
|
|
||||||
- Raises useful errors for unbalanced parentheses
|
|
||||||
- Includes unit tests
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
- Parses numbers
|
|
||||||
- Parses symbols
|
|
||||||
- Parses nested lists
|
|
||||||
- Raises useful errors for unbalanced parentheses
|
|
||||||
- Includes unit tests
|
|
||||||
|
|
||||||
## Project Context
|
|
||||||
|
|
||||||
# Project Context
|
|
||||||
|
|
||||||
## Task Context
|
|
||||||
|
|
||||||
# Task Context
|
|
||||||
|
|
||||||
Task: `TASK-001`
|
|
||||||
Title: Parse Lisp expressions
|
|
||||||
|
|
||||||
## Description
|
|
||||||
|
|
||||||
Implement tokenization and parsing for a tiny Lisp subset.
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
- Parses numbers
|
|
||||||
- Parses symbols
|
|
||||||
- Parses nested lists
|
|
||||||
- Raises useful errors for unbalanced parentheses
|
|
||||||
- Includes unit tests
|
|
||||||
|
|
||||||
## Previous Stage Output
|
|
||||||
|
|
||||||
## repo_lookup_results
|
|
||||||
|
|
||||||
# Repo Tool Calls
|
|
||||||
|
|
||||||
## 1. read_file
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
- path: `lisp_parser.py`
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
```text
|
|
||||||
File not found: lisp_parser.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2. read_file
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
- path: `tests/test_lisp_parser.py`
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
```text
|
|
||||||
File not found: tests/test_lisp_parser.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## 3. list_files
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
- path: `.`
|
|
||||||
- pattern: `*.py`
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
```text
|
|
||||||
lisp.py
|
|
||||||
tests/__init__.py
|
|
||||||
tests/test_lisp.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4. grep
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
- path: `.`
|
|
||||||
- pattern: `def parse\(`
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
```text
|
|
||||||
No matches found.
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5. grep
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
- path: `.`
|
|
||||||
- pattern: `class LispParser`
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
```text
|
|
||||||
No matches found.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Retry Notes
|
|
||||||
|
|
||||||
- Repository lookup results have been provided. Write the final plan now; do not request more lookups.
|
|
||||||
|
|
||||||
## Output Contract
|
|
||||||
|
|
||||||
Write the requested stage output in concise markdown.
|
|
||||||
|
|
||||||
If you need repository context before finalizing the plan, include:
|
|
||||||
lookup_requests:
|
|
||||||
- tool: list_files | read_file | grep
|
|
||||||
path: <relative path>
|
|
||||||
pattern: <glob for list_files or regex for grep>
|
|
||||||
|
|
||||||
Use at most 5 lookup requests.
|
|
||||||
Do not repeat the same lookup request.
|
|
||||||
Prefer read_file for likely-relevant files over many grep variations.
|
|
||||||
Do not search .nightshift, .git, virtualenvs, caches, or artifact directories.
|
|
||||||
|
|
||||||
NightShift will run these read-only lookup tools, save files-inspected.md, and re-run this planner stage with the retrieved context.
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## context
|
|
||||||
|
|
||||||
# Context Pack
|
|
||||||
|
|
||||||
Task: `TASK-001`
|
|
||||||
Title: Parse Lisp expressions
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
- Parses numbers
|
|
||||||
- Parses symbols
|
|
||||||
- Parses nested lists
|
|
||||||
- Raises useful errors for unbalanced parentheses
|
|
||||||
- Includes unit tests
|
|
||||||
|
|
||||||
## Constraints
|
|
||||||
|
|
||||||
- Scoped paths: .
|
|
||||||
- Repository lookups are read-only.
|
|
||||||
- Excerpts are line-numbered where files are read directly.
|
|
||||||
|
|
||||||
## Relevant Files
|
|
||||||
|
|
||||||
```text
|
|
||||||
lisp.py
|
|
||||||
tests/__init__.py
|
|
||||||
tests/test_lisp.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## Search Results
|
|
||||||
|
|
||||||
### Search: parse
|
|
||||||
|
|
||||||
```text
|
|
||||||
tasks.md:21: Evaluate parsed arithmetic expressions.
|
|
||||||
```
|
|
||||||
|
|
||||||
### Search: lisp
|
|
||||||
|
|
||||||
```text
|
|
||||||
nightshift.yaml:2: name: tiny-lisp
|
|
||||||
```
|
|
||||||
|
|
||||||
### Search: expressions
|
|
||||||
|
|
||||||
```text
|
|
||||||
tasks.md:3: - [ ] TASK-001: Parse Lisp expressions
|
|
||||||
tasks.md:21: Evaluate parsed arithmetic expressions.
|
|
||||||
tasks.md:47: Implement simple truthiness and `if` expressions.
|
|
||||||
```
|
|
||||||
|
|
||||||
### Search: parses
|
|
||||||
|
|
||||||
```text
|
|
||||||
No matches found.
|
|
||||||
```
|
|
||||||
|
|
||||||
### Search: numbers
|
|
||||||
|
|
||||||
```text
|
|
||||||
tasks.md:9: - Parses numbers
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## implement
|
|
||||||
|
|
||||||
diff --git a/lisp.py b/lisp.py
|
|
||||||
index 0000000..f2a5c3d
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/lisp.py
|
|
||||||
@@ -0,0 +1,78 @@
|
|
||||||
+import re
|
|
||||||
+
|
|
||||||
+class LispParser:
|
|
||||||
+ def __init__(self, expression):
|
|
||||||
+ self.expression = expression
|
|
||||||
+ self.tokens = self.tokenize()
|
|
||||||
+ self.index = 0
|
|
||||||
+
|
|
||||||
+ def tokenize(self):
|
|
||||||
+ token_pattern = r'\s*([()])|(\d+)|([a-zA-Z_][a-zA-Z0-9_]*)'
|
|
||||||
+ tokens = re.findall(token_pattern, self.expression)
|
|
||||||
+ return [token for group in tokens for token in group if token]
|
|
||||||
+
|
|
||||||
+ def parse(self):
|
|
||||||
+ result = []
|
|
||||||
+ while self.index < len(self.tokens):
|
|
||||||
+ token = self.tokens[self.index]
|
|
||||||
+ if token == '(':
|
|
||||||
+ self.index += 1
|
|
||||||
+ result.append(self.parse())
|
|
||||||
+ elif token == ')':
|
|
||||||
+ return result
|
|
||||||
+ else:
|
|
||||||
+ result.append(self.parse_atom(token))
|
|
||||||
+ self.index += 1
|
|
||||||
+ if len(result) == 1:
|
|
||||||
+ return result[0]
|
|
||||||
+ return result
|
|
||||||
+
|
|
||||||
+ def parse_atom(self, token):
|
|
||||||
+ try:
|
|
||||||
+ return int(token)
|
|
||||||
+ except ValueError:
|
|
||||||
+ return token
|
|
||||||
+
|
|
||||||
+def parse_lisp(expression):
|
|
||||||
+ parser = LispParser(expression)
|
|
||||||
+ return parser.parse()
|
|
||||||
+
|
|
||||||
+if __name__ == "__main__":
|
|
||||||
+ expression = "(1 2 (3 4) a b)"
|
|
||||||
+ print(parse_lisp(expression))
|
|
||||||
diff --git a/tests/test_lisp.py b/tests/test_lisp.py
|
|
||||||
index 0000000..b5c7d8e
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/tests/test_lisp.py
|
|
||||||
@@ -0,0 +1,39 @@
|
|
||||||
+import unittest
|
|
||||||
+from lisp import parse_lisp
|
|
||||||
+
|
|
||||||
+class TestLispParser(unittest.TestCase):
|
|
||||||
+ def test_parse_numbers(self):
|
|
||||||
+ self.assertEqual(parse_lisp("123"), 123)
|
|
||||||
+ self.assertEqual(parse_lisp("(1 2 3)"), [1, 2, 3])
|
|
||||||
+
|
|
||||||
+ def test_parse_symbols(self):
|
|
||||||
+ self.assertEqual(parse_lisp("a"), "a")
|
|
||||||
+ self.assertEqual(parse_lisp("(a b c)"), ["a", "b", "c"])
|
|
||||||
+
|
|
||||||
+ def test_parse_nested_lists(self):
|
|
||||||
+ self.assertEqual(parse_lisp("(1 (2 3) 4)"), [1, [2, 3], 4])
|
|
||||||
+ self.assertEqual(parse_lisp("((a b) (c d))"), [["a", "b"], ["c", "d"]])
|
|
||||||
+
|
|
||||||
+ def test_unbalanced_parentheses(self):
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp("(")
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp(")")
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp("(1 2 (3 4)")
|
|
||||||
+
|
|
||||||
+if __name__ == "__main__":
|
|
||||||
+ unittest.main()
|
|
||||||
|
|
||||||
|
|
||||||
## normalize
|
|
||||||
|
|
||||||
diff --git a/lisp.py b/lisp.py
|
|
||||||
index 0000000..f2a5c3d
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/lisp.py
|
|
||||||
@@ -0,0 +1,78 @@
|
|
||||||
+import re
|
|
||||||
+
|
|
||||||
+class LispParser:
|
|
||||||
+ def __init__(self, expression):
|
|
||||||
+ self.expression = expression
|
|
||||||
+ self.tokens = self.tokenize()
|
|
||||||
+ self.index = 0
|
|
||||||
+
|
|
||||||
+ def tokenize(self):
|
|
||||||
+ token_pattern = r'\s*([()])|(\d+)|([a-zA-Z_][a-zA-Z0-9_]*)'
|
|
||||||
+ tokens = re.findall(token_pattern, self.expression)
|
|
||||||
+ return [token for group in tokens for token in group if token]
|
|
||||||
+
|
|
||||||
+ def parse(self):
|
|
||||||
+ result = []
|
|
||||||
+ while self.index < len(self.tokens):
|
|
||||||
+ token = self.tokens[self.index]
|
|
||||||
+ if token == '(':
|
|
||||||
+ self.index += 1
|
|
||||||
+ result.append(self.parse())
|
|
||||||
+ elif token == ')':
|
|
||||||
+ return result
|
|
||||||
+ else:
|
|
||||||
+ result.append(self.parse_atom(token))
|
|
||||||
+ self.index += 1
|
|
||||||
+ if len(result) == 1:
|
|
||||||
+ return result[0]
|
|
||||||
+ return result
|
|
||||||
+
|
|
||||||
+ def parse_atom(self, token):
|
|
||||||
+ try:
|
|
||||||
+ return int(token)
|
|
||||||
+ except ValueError:
|
|
||||||
+ return token
|
|
||||||
+
|
|
||||||
+def parse_lisp(expression):
|
|
||||||
+ parser = LispParser(expression)
|
|
||||||
+ return parser.parse()
|
|
||||||
+
|
|
||||||
+if __name__ == "__main__":
|
|
||||||
+ expression = "(1 2 (3 4) a b)"
|
|
||||||
+ print(parse_lisp(expression))
|
|
||||||
diff --git a/tests/test_lisp.py b/tests/test_lisp.py
|
|
||||||
index 0000000..b5c7d8e
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/tests/test_lisp.py
|
|
||||||
@@ -0,0 +1,39 @@
|
|
||||||
+import unittest
|
|
||||||
+from lisp import parse_lisp
|
|
||||||
+
|
|
||||||
+class TestLispParser(unittest.TestCase):
|
|
||||||
+ def test_parse_numbers(self):
|
|
||||||
+ self.assertEqual(parse_lisp("123"), 123)
|
|
||||||
+ self.assertEqual(parse_lisp("(1 2 3)"), [1, 2, 3])
|
|
||||||
+
|
|
||||||
+ def test_parse_symbols(self):
|
|
||||||
+ self.assertEqual(parse_lisp("a"), "a")
|
|
||||||
+ self.assertEqual(parse_lisp("(a b c)"), ["a", "b", "c"])
|
|
||||||
+
|
|
||||||
+ def test_parse_nested_lists(self):
|
|
||||||
+ self.assertEqual(parse_lisp("(1 (2 3) 4)"), [1, [2, 3], 4])
|
|
||||||
+ self.assertEqual(parse_lisp("((a b) (c d))"), [["a", "b"], ["c", "d"]])
|
|
||||||
+
|
|
||||||
+ def test_unbalanced_parentheses(self):
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp("(")
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp(")")
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp("(1 2 (3 4)")
|
|
||||||
+
|
|
||||||
+if __name__ == "__main__":
|
|
||||||
+ unittest.main()
|
|
||||||
|
|
||||||
|
|
||||||
## validate_patch
|
|
||||||
|
|
||||||
# Patch Validation
|
|
||||||
|
|
||||||
Status: pass
|
|
||||||
Changed files: 2
|
|
||||||
Changed lines: 68
|
|
||||||
|
|
||||||
## Files
|
|
||||||
|
|
||||||
- `lisp.py`
|
|
||||||
- `tests/test_lisp.py`
|
|
||||||
|
|
||||||
|
|
||||||
## apply_patch
|
|
||||||
|
|
||||||
# Patch Apply
|
|
||||||
|
|
||||||
Status: fail
|
|
||||||
Mode: apply
|
|
||||||
Patch: `.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch`
|
|
||||||
Command: `git apply --ignore-whitespace C:\Users\metis\Documents\tiny-lisp-nightshift\.nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\applied-2.patch`
|
|
||||||
Exit code: 128
|
|
||||||
|
|
||||||
## stdout
|
|
||||||
|
|
||||||
```text
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## stderr
|
|
||||||
|
|
||||||
```text
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:6: trailing whitespace.
|
|
||||||
import re
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:7: trailing whitespace.
|
|
||||||
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:8: trailing whitespace.
|
|
||||||
class LispParser:
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:9: trailing whitespace.
|
|
||||||
def __init__(self, expression):
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:10: trailing whitespace.
|
|
||||||
self.expression = expression
|
|
||||||
error: corrupt patch at line 48
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## context-pack.md
|
|
||||||
|
|
||||||
# Context Pack
|
|
||||||
|
|
||||||
Task: `TASK-001`
|
|
||||||
Title: Parse Lisp expressions
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
- Parses numbers
|
|
||||||
- Parses symbols
|
|
||||||
- Parses nested lists
|
|
||||||
- Raises useful errors for unbalanced parentheses
|
|
||||||
- Includes unit tests
|
|
||||||
|
|
||||||
## Constraints
|
|
||||||
|
|
||||||
- Scoped paths: .
|
|
||||||
- Repository lookups are read-only.
|
|
||||||
- Excerpts are line-numbered where files are read directly.
|
|
||||||
|
|
||||||
## Relevant Files
|
|
||||||
|
|
||||||
```text
|
|
||||||
lisp.py
|
|
||||||
tests/__init__.py
|
|
||||||
tests/test_lisp.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## Search Results
|
|
||||||
|
|
||||||
### Search: parse
|
|
||||||
|
|
||||||
```text
|
|
||||||
tasks.md:21: Evaluate parsed arithmetic expressions.
|
|
||||||
```
|
|
||||||
|
|
||||||
### Search: lisp
|
|
||||||
|
|
||||||
```text
|
|
||||||
nightshift.yaml:2: name: tiny-lisp
|
|
||||||
```
|
|
||||||
|
|
||||||
### Search: expressions
|
|
||||||
|
|
||||||
```text
|
|
||||||
tasks.md:3: - [ ] TASK-001: Parse Lisp expressions
|
|
||||||
tasks.md:21: Evaluate parsed arithmetic expressions.
|
|
||||||
tasks.md:47: Implement simple truthiness and `if` expressions.
|
|
||||||
```
|
|
||||||
|
|
||||||
### Search: parses
|
|
||||||
|
|
||||||
```text
|
|
||||||
No matches found.
|
|
||||||
```
|
|
||||||
|
|
||||||
### Search: numbers
|
|
||||||
|
|
||||||
```text
|
|
||||||
tasks.md:9: - Parses numbers
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## project-context-chart.md
|
|
||||||
|
|
||||||
# Project Context Chart
|
|
||||||
|
|
||||||
## Entry Points
|
|
||||||
|
|
||||||
- `tests/test_lisp.py`: class SmokeTests(unittest.TestCase):
|
|
||||||
|
|
||||||
## Tests
|
|
||||||
|
|
||||||
- `tests/__init__.py`
|
|
||||||
- `tests/test_lisp.py`
|
|
||||||
|
|
||||||
## Files
|
|
||||||
|
|
||||||
### `agents/implementer.md`
|
|
||||||
|
|
||||||
- Responsibility: You are the implementation agent for NightShift.
|
|
||||||
- Entry point: false
|
|
||||||
- Test file: false
|
|
||||||
- Functions: None detected
|
|
||||||
- Classes: None detected
|
|
||||||
- Anchors/search terms: None detected
|
|
||||||
|
|
||||||
### `agents/planner.md`
|
|
||||||
|
|
||||||
- Responsibility: You are the planning agent for NightShift.
|
|
||||||
- Entry point: false
|
|
||||||
- Test file: false
|
|
||||||
- Functions: None detected
|
|
||||||
- Classes: None detected
|
|
||||||
- Anchors/search terms: None detected
|
|
||||||
|
|
||||||
### `agents/reviewer.md`
|
|
||||||
|
|
||||||
- Responsibility: You are the review agent for NightShift.
|
|
||||||
- Entry point: false
|
|
||||||
- Test file: false
|
|
||||||
- Functions: None detected
|
|
||||||
- Classes: None detected
|
|
||||||
- Anchors/search terms: None detected
|
|
||||||
|
|
||||||
### `lisp.py`
|
|
||||||
|
|
||||||
- Responsibility: """Tiny Lisp quickstart target.
|
|
||||||
- Entry point: false
|
|
||||||
- Test file: false
|
|
||||||
- Functions: None detected
|
|
||||||
- Classes: None detected
|
|
||||||
- Anchors/search terms: None detected
|
|
||||||
|
|
||||||
### `nightshift.yaml`
|
|
||||||
|
|
||||||
- Responsibility: project:
|
|
||||||
- Entry point: false
|
|
||||||
- Test file: false
|
|
||||||
- Functions: None detected
|
|
||||||
- Classes: None detected
|
|
||||||
- Anchors/search terms: None detected
|
|
||||||
|
|
||||||
### `tasks.md`
|
|
||||||
|
|
||||||
- Responsibility: Tasks
|
|
||||||
- Entry point: false
|
|
||||||
- Test file: false
|
|
||||||
- Functions: None detected
|
|
||||||
- Classes: None detected
|
|
||||||
- Anchors/search terms: None detected
|
|
||||||
|
|
||||||
### `tests/__init__.py`
|
|
||||||
|
|
||||||
- Responsibility: """Quickstart Lisp test suite."""
|
|
||||||
- Entry point: false
|
|
||||||
- Test file: true
|
|
||||||
- Functions: None detected
|
|
||||||
- Classes: None detected
|
|
||||||
- Anchors/search terms: None detected
|
|
||||||
|
|
||||||
### `tests/test_lisp.py`
|
|
||||||
|
|
||||||
- Responsibility: class SmokeTests(unittest.TestCase):
|
|
||||||
- Entry point: true
|
|
||||||
- Test file: true
|
|
||||||
- Functions: test_smoke@L5
|
|
||||||
- Classes: SmokeTests@L4
|
|
||||||
- Anchors/search terms: SmokeTests, test_smoke
|
|
||||||
|
|
||||||
## Retry Notes
|
|
||||||
|
|
||||||
- Context update from 'implement': Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/implementation-summary.md
|
|
||||||
- Retry 1: stage 'validate_patch' returned fail (Patch validation failed: patch creates existing file `lisp.py`.); redirecting to 'implement'.
|
|
||||||
- Context update from 'implement': Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-summary-1.md
|
|
||||||
- Context update from 'apply_patch': C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:6: trailing whitespace.
|
|
||||||
import re
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:7: trailing whitespace.
|
|
||||||
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:8: trailing whitespace.
|
|
||||||
class LispParser:
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:9: trailing whitespace.
|
|
||||||
def __init__(self, expression):
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:10: trailing whitespace.
|
|
||||||
self.expression = expression
|
|
||||||
error: corrupt patch at line 48
|
|
||||||
- Retry 2: stage 'apply_patch' returned fail (Patch apply failed with code 128.); redirecting to 'implement'.
|
|
||||||
- Context update from 'implement': Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-summary-2.md
|
|
||||||
- Context update from 'apply_patch': C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:6: trailing whitespace.
|
|
||||||
import re
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:7: trailing whitespace.
|
|
||||||
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:8: trailing whitespace.
|
|
||||||
class LispParser:
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:9: trailing whitespace.
|
|
||||||
def __init__(self, expression):
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:10: trailing whitespace.
|
|
||||||
self.expression = expression
|
|
||||||
error: corrupt patch at line 48
|
|
||||||
- Retry 3: stage 'apply_patch' returned fail (Patch apply failed with code 128.); redirecting to 'implement'.
|
|
||||||
|
|
||||||
## Output Contract
|
|
||||||
|
|
||||||
Return a unified diff only, suitable for saving as proposed.patch or repair-N.patch.
|
|
||||||
Do not include prose outside the patch.
|
|
||||||
Use diff --git headers and hunk headers.
|
|
||||||
For existing files, do not use new file mode or /dev/null headers.
|
|
||||||
On repair attempts, return a complete corrected replacement diff.
|
|
||||||
```
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
diff --git a/lisp.py b/lisp.py
|
|
||||||
index 0000000..f2a5c3d
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/lisp.py
|
|
||||||
@@ -0,0 +1,78 @@
|
|
||||||
+import re
|
|
||||||
+
|
|
||||||
+class LispParser:
|
|
||||||
+ def __init__(self, expression):
|
|
||||||
+ self.expression = expression
|
|
||||||
+ self.tokens = self.tokenize()
|
|
||||||
+ self.index = 0
|
|
||||||
+
|
|
||||||
+ def tokenize(self):
|
|
||||||
+ token_pattern = r'\s*([()])|(\d+)|([a-zA-Z_][a-zA-Z0-9_]*)'
|
|
||||||
+ tokens = re.findall(token_pattern, self.expression)
|
|
||||||
+ return [token for group in tokens for token in group if token]
|
|
||||||
+
|
|
||||||
+ def parse(self):
|
|
||||||
+ result = []
|
|
||||||
+ while self.index < len(self.tokens):
|
|
||||||
+ token = self.tokens[self.index]
|
|
||||||
+ if token == '(':
|
|
||||||
+ self.index += 1
|
|
||||||
+ result.append(self.parse())
|
|
||||||
+ elif token == ')':
|
|
||||||
+ return result
|
|
||||||
+ else:
|
|
||||||
+ result.append(self.parse_atom(token))
|
|
||||||
+ self.index += 1
|
|
||||||
+ if len(result) == 1:
|
|
||||||
+ return result[0]
|
|
||||||
+ return result
|
|
||||||
+
|
|
||||||
+ def parse_atom(self, token):
|
|
||||||
+ try:
|
|
||||||
+ return int(token)
|
|
||||||
+ except ValueError:
|
|
||||||
+ return token
|
|
||||||
+
|
|
||||||
+def parse_lisp(expression):
|
|
||||||
+ parser = LispParser(expression)
|
|
||||||
+ return parser.parse()
|
|
||||||
+
|
|
||||||
+if __name__ == "__main__":
|
|
||||||
+ expression = "(1 2 (3 4) a b)"
|
|
||||||
+ print(parse_lisp(expression))
|
|
||||||
diff --git a/tests/test_lisp.py b/tests/test_lisp.py
|
|
||||||
index 0000000..b5c7d8e
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/tests/test_lisp.py
|
|
||||||
@@ -0,0 +1,39 @@
|
|
||||||
+import unittest
|
|
||||||
+from lisp import parse_lisp
|
|
||||||
+
|
|
||||||
+class TestLispParser(unittest.TestCase):
|
|
||||||
+ def test_parse_numbers(self):
|
|
||||||
+ self.assertEqual(parse_lisp("123"), 123)
|
|
||||||
+ self.assertEqual(parse_lisp("(1 2 3)"), [1, 2, 3])
|
|
||||||
+
|
|
||||||
+ def test_parse_symbols(self):
|
|
||||||
+ self.assertEqual(parse_lisp("a"), "a")
|
|
||||||
+ self.assertEqual(parse_lisp("(a b c)"), ["a", "b", "c"])
|
|
||||||
+
|
|
||||||
+ def test_parse_nested_lists(self):
|
|
||||||
+ self.assertEqual(parse_lisp("(1 (2 3) 4)"), [1, [2, 3], 4])
|
|
||||||
+ self.assertEqual(parse_lisp("((a b) (c d))"), [["a", "b"], ["c", "d"]])
|
|
||||||
+
|
|
||||||
+ def test_unbalanced_parentheses(self):
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp("(")
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp(")")
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp("(1 2 (3 4)")
|
|
||||||
+
|
|
||||||
+if __name__ == "__main__":
|
|
||||||
+ unittest.main()
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
diff --git a/lisp.py b/lisp.py
|
|
||||||
index 0000000..f2a5c3d
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/lisp.py
|
|
||||||
@@ -0,0 +1,78 @@
|
|
||||||
+import re
|
|
||||||
+
|
|
||||||
+class LispParser:
|
|
||||||
+ def __init__(self, expression):
|
|
||||||
+ self.expression = expression
|
|
||||||
+ self.tokens = self.tokenize()
|
|
||||||
+ self.index = 0
|
|
||||||
+
|
|
||||||
+ def tokenize(self):
|
|
||||||
+ token_pattern = r'\s*([()])|(\d+)|([a-zA-Z_][a-zA-Z0-9_]*)'
|
|
||||||
+ tokens = re.findall(token_pattern, self.expression)
|
|
||||||
+ return [token for group in tokens for token in group if token]
|
|
||||||
+
|
|
||||||
+ def parse(self):
|
|
||||||
+ result = []
|
|
||||||
+ while self.index < len(self.tokens):
|
|
||||||
+ token = self.tokens[self.index]
|
|
||||||
+ if token == '(':
|
|
||||||
+ self.index += 1
|
|
||||||
+ result.append(self.parse())
|
|
||||||
+ elif token == ')':
|
|
||||||
+ return result
|
|
||||||
+ else:
|
|
||||||
+ result.append(self.parse_atom(token))
|
|
||||||
+ self.index += 1
|
|
||||||
+ if len(result) == 1:
|
|
||||||
+ return result[0]
|
|
||||||
+ return result
|
|
||||||
+
|
|
||||||
+ def parse_atom(self, token):
|
|
||||||
+ try:
|
|
||||||
+ return int(token)
|
|
||||||
+ except ValueError:
|
|
||||||
+ return token
|
|
||||||
+
|
|
||||||
+def parse_lisp(expression):
|
|
||||||
+ parser = LispParser(expression)
|
|
||||||
+ return parser.parse()
|
|
||||||
+
|
|
||||||
+if __name__ == "__main__":
|
|
||||||
+ expression = "(1 2 (3 4) a b)"
|
|
||||||
+ print(parse_lisp(expression))
|
|
||||||
diff --git a/tests/test_lisp.py b/tests/test_lisp.py
|
|
||||||
index 0000000..b5c7d8e
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/tests/test_lisp.py
|
|
||||||
@@ -0,0 +1,39 @@
|
|
||||||
+import unittest
|
|
||||||
+from lisp import parse_lisp
|
|
||||||
+
|
|
||||||
+class TestLispParser(unittest.TestCase):
|
|
||||||
+ def test_parse_numbers(self):
|
|
||||||
+ self.assertEqual(parse_lisp("123"), 123)
|
|
||||||
+ self.assertEqual(parse_lisp("(1 2 3)"), [1, 2, 3])
|
|
||||||
+
|
|
||||||
+ def test_parse_symbols(self):
|
|
||||||
+ self.assertEqual(parse_lisp("a"), "a")
|
|
||||||
+ self.assertEqual(parse_lisp("(a b c)"), ["a", "b", "c"])
|
|
||||||
+
|
|
||||||
+ def test_parse_nested_lists(self):
|
|
||||||
+ self.assertEqual(parse_lisp("(1 (2 3) 4)"), [1, [2, 3], 4])
|
|
||||||
+ self.assertEqual(parse_lisp("((a b) (c d))"), [["a", "b"], ["c", "d"]])
|
|
||||||
+
|
|
||||||
+ def test_unbalanced_parentheses(self):
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp("(")
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp(")")
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp("(1 2 (3 4)")
|
|
||||||
+
|
|
||||||
+if __name__ == "__main__":
|
|
||||||
+ unittest.main()
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
diff --git a/lisp.py b/lisp.py
|
|
||||||
index 0000000..f2a5c3d
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/lisp.py
|
|
||||||
@@ -0,0 +1,78 @@
|
|
||||||
+import re
|
|
||||||
+
|
|
||||||
+class LispParser:
|
|
||||||
+ def __init__(self, expression):
|
|
||||||
+ self.expression = expression
|
|
||||||
+ self.tokens = self.tokenize()
|
|
||||||
+ self.index = 0
|
|
||||||
+
|
|
||||||
+ def tokenize(self):
|
|
||||||
+ token_pattern = r'\s*([()])|(\d+)|([a-zA-Z_][a-zA-Z0-9_]*)'
|
|
||||||
+ tokens = re.findall(token_pattern, self.expression)
|
|
||||||
+ return [token for group in tokens for token in group if token]
|
|
||||||
+
|
|
||||||
+ def parse(self):
|
|
||||||
+ result = []
|
|
||||||
+ while self.index < len(self.tokens):
|
|
||||||
+ token = self.tokens[self.index]
|
|
||||||
+ if token == '(':
|
|
||||||
+ self.index += 1
|
|
||||||
+ result.append(self.parse())
|
|
||||||
+ elif token == ')':
|
|
||||||
+ return result
|
|
||||||
+ else:
|
|
||||||
+ result.append(self.parse_atom(token))
|
|
||||||
+ self.index += 1
|
|
||||||
+ if len(result) == 1:
|
|
||||||
+ return result[0]
|
|
||||||
+ return result
|
|
||||||
+
|
|
||||||
+ def parse_atom(self, token):
|
|
||||||
+ try:
|
|
||||||
+ return int(token)
|
|
||||||
+ except ValueError:
|
|
||||||
+ return token
|
|
||||||
+
|
|
||||||
+def parse_lisp(expression):
|
|
||||||
+ parser = LispParser(expression)
|
|
||||||
+ return parser.parse()
|
|
||||||
+
|
|
||||||
+if __name__ == "__main__":
|
|
||||||
+ expression = "(1 2 (3 4) a b)"
|
|
||||||
+ print(parse_lisp(expression))
|
|
||||||
diff --git a/tests/test_lisp.py b/tests/test_lisp.py
|
|
||||||
index 0000000..b5c7d8e
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/tests/test_lisp.py
|
|
||||||
@@ -0,0 +1,39 @@
|
|
||||||
+import unittest
|
|
||||||
+from lisp import parse_lisp
|
|
||||||
+
|
|
||||||
+class TestLispParser(unittest.TestCase):
|
|
||||||
+ def test_parse_numbers(self):
|
|
||||||
+ self.assertEqual(parse_lisp("123"), 123)
|
|
||||||
+ self.assertEqual(parse_lisp("(1 2 3)"), [1, 2, 3])
|
|
||||||
+
|
|
||||||
+ def test_parse_symbols(self):
|
|
||||||
+ self.assertEqual(parse_lisp("a"), "a")
|
|
||||||
+ self.assertEqual(parse_lisp("(a b c)"), ["a", "b", "c"])
|
|
||||||
+
|
|
||||||
+ def test_parse_nested_lists(self):
|
|
||||||
+ self.assertEqual(parse_lisp("(1 (2 3) 4)"), [1, [2, 3], 4])
|
|
||||||
+ self.assertEqual(parse_lisp("((a b) (c d))"), [["a", "b"], ["c", "d"]])
|
|
||||||
+
|
|
||||||
+ def test_unbalanced_parentheses(self):
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp("(")
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp(")")
|
|
||||||
+ with self.assertRaises(IndexError):
|
|
||||||
+ parse_lisp("(1 2 (3 4)")
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
# Implementation Summary
|
|
||||||
|
|
||||||
Stage: `implement`
|
|
||||||
Status: pass
|
|
||||||
Repair attempt: 1
|
|
||||||
Patch: `.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-1.patch`
|
|
||||||
|
|
||||||
## Retry Feedback
|
|
||||||
|
|
||||||
- Context update from 'implement': Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/implementation-summary.md
|
|
||||||
- Retry 1: stage 'validate_patch' returned fail (Patch validation failed: patch creates existing file `lisp.py`.); redirecting to 'implement'.
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
# Implementation Summary
|
|
||||||
|
|
||||||
Stage: `implement`
|
|
||||||
Status: pass
|
|
||||||
Repair attempt: 2
|
|
||||||
Patch: `.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-2.patch`
|
|
||||||
|
|
||||||
## Retry Feedback
|
|
||||||
|
|
||||||
- Context update from 'implement': Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/implementation-summary.md
|
|
||||||
- Retry 1: stage 'validate_patch' returned fail (Patch validation failed: patch creates existing file `lisp.py`.); redirecting to 'implement'.
|
|
||||||
- Context update from 'implement': Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-summary-1.md
|
|
||||||
- Context update from 'apply_patch': C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:6: trailing whitespace.
|
|
||||||
import re
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:7: trailing whitespace.
|
|
||||||
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:8: trailing whitespace.
|
|
||||||
class LispParser:
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:9: trailing whitespace.
|
|
||||||
def __init__(self, expression):
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:10: trailing whitespace.
|
|
||||||
self.expression = expression
|
|
||||||
error: corrupt patch at line 48
|
|
||||||
- Retry 2: stage 'apply_patch' returned fail (Patch apply failed with code 128.); redirecting to 'implement'.
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
# Implementation Summary
|
|
||||||
|
|
||||||
Stage: `implement`
|
|
||||||
Status: pass
|
|
||||||
Repair attempt: 3
|
|
||||||
Patch: `.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-3.patch`
|
|
||||||
|
|
||||||
## Retry Feedback
|
|
||||||
|
|
||||||
- Context update from 'apply_patch': C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:6: trailing whitespace.
|
|
||||||
import re
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:7: trailing whitespace.
|
|
||||||
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:8: trailing whitespace.
|
|
||||||
class LispParser:
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:9: trailing whitespace.
|
|
||||||
def __init__(self, expression):
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:10: trailing whitespace.
|
|
||||||
self.expression = expression
|
|
||||||
error: corrupt patch at line 48
|
|
||||||
- Retry 2: stage 'apply_patch' returned fail (Patch apply failed with code 128.); redirecting to 'implement'.
|
|
||||||
- Context update from 'implement': Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-summary-2.md
|
|
||||||
- Context update from 'apply_patch': C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:6: trailing whitespace.
|
|
||||||
import re
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:7: trailing whitespace.
|
|
||||||
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:8: trailing whitespace.
|
|
||||||
class LispParser:
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:9: trailing whitespace.
|
|
||||||
def __init__(self, expression):
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:10: trailing whitespace.
|
|
||||||
self.expression = expression
|
|
||||||
error: corrupt patch at line 48
|
|
||||||
- Retry 3: stage 'apply_patch' returned fail (Patch apply failed with code 128.); redirecting to 'implement'.
|
|
||||||
|
|
@ -1,172 +0,0 @@
|
||||||
# Stage Results
|
|
||||||
|
|
||||||
Task: `TASK-001`
|
|
||||||
Status: failed
|
|
||||||
Retry count: 3
|
|
||||||
Reason: Retry limit reached after stage 'apply_patch': Patch apply failed with code 128.
|
|
||||||
|
|
||||||
## plan
|
|
||||||
|
|
||||||
Status: pass
|
|
||||||
Reason: Agent completed after repo lookup.
|
|
||||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\plan.md
|
|
||||||
Next stage:
|
|
||||||
Context update:
|
|
||||||
|
|
||||||
## context
|
|
||||||
|
|
||||||
Status: pass
|
|
||||||
Reason: Context pack written.
|
|
||||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\context-pack.md
|
|
||||||
Next stage:
|
|
||||||
Context update:
|
|
||||||
|
|
||||||
## implement
|
|
||||||
|
|
||||||
Status: pass
|
|
||||||
Reason: Proposed patch written.
|
|
||||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\proposed.patch
|
|
||||||
Next stage:
|
|
||||||
Context update: Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/implementation-summary.md
|
|
||||||
|
|
||||||
## normalize
|
|
||||||
|
|
||||||
Status: pass
|
|
||||||
Reason: Normalized patch written.
|
|
||||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\normalized.patch
|
|
||||||
Next stage:
|
|
||||||
Context update:
|
|
||||||
|
|
||||||
## validate_patch
|
|
||||||
|
|
||||||
Status: fail
|
|
||||||
Reason: Patch validation failed: patch creates existing file `lisp.py`.
|
|
||||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\patch-validation.md
|
|
||||||
Next stage:
|
|
||||||
Context update:
|
|
||||||
|
|
||||||
## implement
|
|
||||||
|
|
||||||
Status: pass
|
|
||||||
Reason: Proposed patch written.
|
|
||||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\repair-1.patch
|
|
||||||
Next stage:
|
|
||||||
Context update: Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-summary-1.md
|
|
||||||
|
|
||||||
## normalize
|
|
||||||
|
|
||||||
Status: pass
|
|
||||||
Reason: Normalized patch written.
|
|
||||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\normalized-1.patch
|
|
||||||
Next stage:
|
|
||||||
Context update:
|
|
||||||
|
|
||||||
## validate_patch
|
|
||||||
|
|
||||||
Status: pass
|
|
||||||
Reason: Patch validation passed.
|
|
||||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\patch-validation-1.md
|
|
||||||
Next stage:
|
|
||||||
Context update:
|
|
||||||
|
|
||||||
## apply_patch
|
|
||||||
|
|
||||||
Status: fail
|
|
||||||
Reason: Patch apply failed with code 128.
|
|
||||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\patch-apply-output-1.txt
|
|
||||||
Next stage:
|
|
||||||
Context update: C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:6: trailing whitespace.
|
|
||||||
import re
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:7: trailing whitespace.
|
|
||||||
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:8: trailing whitespace.
|
|
||||||
class LispParser:
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:9: trailing whitespace.
|
|
||||||
def __init__(self, expression):
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-1.patch:10: trailing whitespace.
|
|
||||||
self.expression = expression
|
|
||||||
error: corrupt patch at line 48
|
|
||||||
|
|
||||||
## implement
|
|
||||||
|
|
||||||
Status: pass
|
|
||||||
Reason: Proposed patch written.
|
|
||||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\repair-2.patch
|
|
||||||
Next stage:
|
|
||||||
Context update: Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-summary-2.md
|
|
||||||
|
|
||||||
## normalize
|
|
||||||
|
|
||||||
Status: pass
|
|
||||||
Reason: Normalized patch written.
|
|
||||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\normalized-2.patch
|
|
||||||
Next stage:
|
|
||||||
Context update:
|
|
||||||
|
|
||||||
## validate_patch
|
|
||||||
|
|
||||||
Status: pass
|
|
||||||
Reason: Patch validation passed.
|
|
||||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\patch-validation-2.md
|
|
||||||
Next stage:
|
|
||||||
Context update:
|
|
||||||
|
|
||||||
## apply_patch
|
|
||||||
|
|
||||||
Status: fail
|
|
||||||
Reason: Patch apply failed with code 128.
|
|
||||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\patch-apply-output-2.txt
|
|
||||||
Next stage:
|
|
||||||
Context update: C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:6: trailing whitespace.
|
|
||||||
import re
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:7: trailing whitespace.
|
|
||||||
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:8: trailing whitespace.
|
|
||||||
class LispParser:
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:9: trailing whitespace.
|
|
||||||
def __init__(self, expression):
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-2.patch:10: trailing whitespace.
|
|
||||||
self.expression = expression
|
|
||||||
error: corrupt patch at line 48
|
|
||||||
|
|
||||||
## implement
|
|
||||||
|
|
||||||
Status: pass
|
|
||||||
Reason: Proposed patch written.
|
|
||||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\repair-3.patch
|
|
||||||
Next stage:
|
|
||||||
Context update: Implementation summary: .nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/repair-summary-3.md
|
|
||||||
|
|
||||||
## normalize
|
|
||||||
|
|
||||||
Status: pass
|
|
||||||
Reason: Normalized patch written.
|
|
||||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\normalized-3.patch
|
|
||||||
Next stage:
|
|
||||||
Context update:
|
|
||||||
|
|
||||||
## validate_patch
|
|
||||||
|
|
||||||
Status: pass
|
|
||||||
Reason: Patch validation passed.
|
|
||||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\patch-validation-3.md
|
|
||||||
Next stage:
|
|
||||||
Context update:
|
|
||||||
|
|
||||||
## apply_patch
|
|
||||||
|
|
||||||
Status: fail
|
|
||||||
Reason: Patch apply failed with code 128.
|
|
||||||
Output: .nightshift\runs\20260517T212359.709956Z\tasks\TASK-001\patch-apply-output-3.txt
|
|
||||||
Next stage:
|
|
||||||
Context update: C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:6: trailing whitespace.
|
|
||||||
import re
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:7: trailing whitespace.
|
|
||||||
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:8: trailing whitespace.
|
|
||||||
class LispParser:
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:9: trailing whitespace.
|
|
||||||
def __init__(self, expression):
|
|
||||||
C:/Users/metis/Documents/tiny-lisp-nightshift/.nightshift/runs/20260517T212359.709956Z/tasks/TASK-001/applied-3.patch:10: trailing whitespace.
|
|
||||||
self.expression = expression
|
|
||||||
error: corrupt patch at line 48
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
# Task Completion
|
|
||||||
|
|
||||||
Task: `TASK-001`
|
|
||||||
Pipeline status: failed
|
|
||||||
Marked complete: false
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
- [ ] TASK-001: Parse Lisp expressions
|
|
||||||
|
|
||||||
Description:
|
|
||||||
Implement tokenization and parsing for a tiny Lisp subset.
|
|
||||||
|
|
||||||
Acceptance Criteria:
|
|
||||||
- Parses numbers
|
|
||||||
- Parses symbols
|
|
||||||
- Parses nested lists
|
|
||||||
- Raises useful errors for unbalanced parentheses
|
|
||||||
- Includes unit tests
|
|
||||||
|
|
@ -113,6 +113,12 @@ nightshift status
|
||||||
nightshift run --task TASK-001
|
nightshift run --task TASK-001
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For the first real-model tutorial target:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nightshift init --template imageboard --root nightshift-imageboard
|
||||||
|
```
|
||||||
|
|
||||||
Open the read-only artifact dashboard:
|
Open the read-only artifact dashboard:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
"""Fake code writer for the NightShift end-to-end quickstart."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
import difflib
|
|
||||||
|
|
||||||
|
|
||||||
FILES = {
|
|
||||||
"lisp.py": '''"""Tiny Lisp parser used by the NightShift quickstart."""
|
|
||||||
|
|
||||||
|
|
||||||
def tokenize(source):
|
|
||||||
spaced = source.replace("(", " ( ").replace(")", " ) ")
|
|
||||||
return spaced.split()
|
|
||||||
|
|
||||||
|
|
||||||
def parse(source):
|
|
||||||
tokens = tokenize(source)
|
|
||||||
if not tokens:
|
|
||||||
raise ValueError("empty expression")
|
|
||||||
expression = _parse_tokens(tokens)
|
|
||||||
if tokens:
|
|
||||||
raise ValueError("unexpected trailing tokens")
|
|
||||||
return expression
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_tokens(tokens):
|
|
||||||
if not tokens:
|
|
||||||
raise ValueError("unexpected end of input")
|
|
||||||
token = tokens.pop(0)
|
|
||||||
if token == "(":
|
|
||||||
values = []
|
|
||||||
while tokens and tokens[0] != ")":
|
|
||||||
values.append(_parse_tokens(tokens))
|
|
||||||
if not tokens:
|
|
||||||
raise ValueError("unbalanced parentheses")
|
|
||||||
tokens.pop(0)
|
|
||||||
return values
|
|
||||||
if token == ")":
|
|
||||||
raise ValueError("unexpected closing parenthesis")
|
|
||||||
return _atom(token)
|
|
||||||
|
|
||||||
|
|
||||||
def _atom(token):
|
|
||||||
try:
|
|
||||||
return int(token)
|
|
||||||
except ValueError:
|
|
||||||
return token
|
|
||||||
''',
|
|
||||||
"tests/test_lisp.py": """import unittest
|
|
||||||
|
|
||||||
from lisp import parse
|
|
||||||
|
|
||||||
|
|
||||||
class ParserTests(unittest.TestCase):
|
|
||||||
def test_parses_numbers(self):
|
|
||||||
self.assertEqual(parse("42"), 42)
|
|
||||||
|
|
||||||
def test_parses_symbols(self):
|
|
||||||
self.assertEqual(parse("answer"), "answer")
|
|
||||||
|
|
||||||
def test_parses_nested_lists(self):
|
|
||||||
self.assertEqual(parse("(+ 1 (* 2 3))"), ["+", 1, ["*", 2, 3]])
|
|
||||||
|
|
||||||
def test_rejects_unbalanced_parentheses(self):
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
parse("(+ 1 2")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
unittest.main()
|
|
||||||
""",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
chunks: list[str] = []
|
|
||||||
for relative_path, desired in FILES.items():
|
|
||||||
path = Path(relative_path)
|
|
||||||
current = path.read_text(encoding="utf-8") if path.exists() else ""
|
|
||||||
chunks.append(f"diff --git a/{relative_path} b/{relative_path}\n")
|
|
||||||
chunks.extend(
|
|
||||||
difflib.unified_diff(
|
|
||||||
current.splitlines(keepends=True),
|
|
||||||
desired.splitlines(keepends=True),
|
|
||||||
fromfile=f"a/{relative_path}",
|
|
||||||
tofile=f"b/{relative_path}",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
print("".join(chunks), end="")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
"""Fake planner for the NightShift end-to-end quickstart."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
prompt = sys.stdin.read()
|
|
||||||
if "repo_lookup_results" in prompt:
|
|
||||||
print("# Plan")
|
|
||||||
print("")
|
|
||||||
print("- Use the context pack and inspected files.")
|
|
||||||
print("- Add parser functions to `lisp.py`.")
|
|
||||||
print("- Replace the smoke test with parser unit tests.")
|
|
||||||
else:
|
|
||||||
print("lookup_requests:")
|
|
||||||
print("- tool: read_file")
|
|
||||||
print(" path: lisp.py")
|
|
||||||
print("- tool: read_file")
|
|
||||||
print(" path: tests/test_lisp.py")
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
You are the implementation agent.
|
|
||||||
|
|
||||||
Output only a unified diff.
|
|
||||||
Implement the smallest correct change and include tests when needed.
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
You are the planning agent. Create a small conservative plan.
|
|
||||||
|
|
||||||
Include:
|
|
||||||
- relevant files
|
|
||||||
- implementation steps
|
|
||||||
- tests
|
|
||||||
- risks
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
You are the review agent.
|
|
||||||
|
|
||||||
Output:
|
|
||||||
status: pass | fail | retry | escalate
|
|
||||||
reason: <short explanation>
|
|
||||||
next_stage: <optional stage id>
|
|
||||||
context_update: <compact useful note>
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
"""Tiny Lisp quickstart target.
|
|
||||||
|
|
||||||
NightShift tasks in this example are intended to fill this module in.
|
|
||||||
"""
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
project:
|
|
||||||
name: tiny-lisp
|
|
||||||
root: .
|
|
||||||
task_file: tasks.md
|
|
||||||
artifact_dir: .nightshift
|
|
||||||
|
|
||||||
safety:
|
|
||||||
require_clean_worktree: false
|
|
||||||
scoped_paths:
|
|
||||||
- .
|
|
||||||
allowed_commands:
|
|
||||||
- python -m unittest discover -v
|
|
||||||
forbidden_commands:
|
|
||||||
- rm -rf
|
|
||||||
- git push
|
|
||||||
- curl | bash
|
|
||||||
|
|
||||||
experiment:
|
|
||||||
label: quickstart-lisp
|
|
||||||
prompt_variant: fake-agent-v1
|
|
||||||
|
|
||||||
agents:
|
|
||||||
planner:
|
|
||||||
backend: command
|
|
||||||
command: python agents/fake_planner.py
|
|
||||||
system_prompt: agents/planner.md
|
|
||||||
|
|
||||||
implementer:
|
|
||||||
backend: command
|
|
||||||
command: python agents/fake_code_writer.py
|
|
||||||
system_prompt: agents/implementer.md
|
|
||||||
|
|
||||||
reviewer:
|
|
||||||
backend: command
|
|
||||||
command: python -c "print('status: pass'); print('reason: quickstart reviewer accepted artifacts')"
|
|
||||||
system_prompt: agents/reviewer.md
|
|
||||||
|
|
||||||
pipeline:
|
|
||||||
max_task_retries: 3
|
|
||||||
continue_on_task_failure: false
|
|
||||||
stages:
|
|
||||||
- id: plan
|
|
||||||
type: agent
|
|
||||||
agent: planner
|
|
||||||
output: plan.md
|
|
||||||
|
|
||||||
- id: context
|
|
||||||
type: repo_context
|
|
||||||
output: context-pack.md
|
|
||||||
|
|
||||||
- id: implement
|
|
||||||
type: code_writer
|
|
||||||
agent: implementer
|
|
||||||
output: proposed.patch
|
|
||||||
|
|
||||||
- id: normalize
|
|
||||||
type: patch_normalizer
|
|
||||||
output: normalized.patch
|
|
||||||
|
|
||||||
- id: validate_patch
|
|
||||||
type: patch_validator
|
|
||||||
output: patch-validation.md
|
|
||||||
max_files: 4
|
|
||||||
max_lines: 400
|
|
||||||
on_fail: implement
|
|
||||||
|
|
||||||
- id: apply_patch
|
|
||||||
type: patch_apply
|
|
||||||
mode: apply
|
|
||||||
output: patch-apply-output.txt
|
|
||||||
on_fail: implement
|
|
||||||
|
|
||||||
- id: test
|
|
||||||
type: command
|
|
||||||
commands:
|
|
||||||
- python -m unittest discover -v
|
|
||||||
output: test-output.txt
|
|
||||||
shell: true
|
|
||||||
timeout_seconds: 60
|
|
||||||
on_fail: implement
|
|
||||||
|
|
||||||
- id: review
|
|
||||||
type: agent_review
|
|
||||||
agent: reviewer
|
|
||||||
on_fail: implement
|
|
||||||
output: review.md
|
|
||||||
|
|
||||||
- id: summarize
|
|
||||||
type: summarize
|
|
||||||
output: final-notes.md
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
# Tasks
|
|
||||||
|
|
||||||
- [ ] TASK-001: Parse Lisp expressions
|
|
||||||
|
|
||||||
Description:
|
|
||||||
Implement tokenization and parsing for a tiny Lisp subset.
|
|
||||||
|
|
||||||
Acceptance Criteria:
|
|
||||||
- Parses numbers
|
|
||||||
- Parses symbols
|
|
||||||
- Parses nested lists
|
|
||||||
- Raises useful errors for unbalanced parentheses
|
|
||||||
- Includes unit tests
|
|
||||||
|
|
||||||
- [ ] TASK-002: Evaluate arithmetic forms
|
|
||||||
|
|
||||||
Dependencies:
|
|
||||||
- TASK-001
|
|
||||||
|
|
||||||
Description:
|
|
||||||
Evaluate parsed arithmetic expressions.
|
|
||||||
|
|
||||||
Acceptance Criteria:
|
|
||||||
- Supports `+`, `-`, `*`, and `/`
|
|
||||||
- Evaluates nested arithmetic
|
|
||||||
- Includes unit tests
|
|
||||||
|
|
||||||
- [ ] TASK-003: Add variables and definitions
|
|
||||||
|
|
||||||
Dependencies:
|
|
||||||
- TASK-002
|
|
||||||
|
|
||||||
Description:
|
|
||||||
Add an environment and support variable lookup and definitions.
|
|
||||||
|
|
||||||
Acceptance Criteria:
|
|
||||||
- Supports symbol lookup
|
|
||||||
- Supports `(define name value)`
|
|
||||||
- Keeps environment behavior tested
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
"""Quickstart Lisp test suite."""
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
import unittest
|
|
||||||
|
|
||||||
|
|
||||||
class SmokeTests(unittest.TestCase):
|
|
||||||
def test_smoke(self):
|
|
||||||
self.assertTrue(True)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
unittest.main()
|
|
||||||
|
|
@ -1,22 +1,40 @@
|
||||||
# Tutorial 01: Building A Small Imageboard With Real Local Models
|
# Tutorial 01: Building A Small Imageboard With Real Local Models
|
||||||
|
|
||||||
This tutorial starts after the quickstart. The quickstart uses fake command agents so you can verify the pipeline deterministically. Here, you will point NightShift at a small web application and let a local model implement one feature slice at a time.
|
This tutorial starts after the quickstart. The quickstart uses fake command agents so you can verify the pipeline deterministically. Here, you will use a named NightShift template and let a local model build the target application.
|
||||||
|
|
||||||
The target is a compact 4chan-style imageboard: boards, threads, replies, images, tripcodes, sessions, reports, and moderation. That is larger than a toy parser, but it is a better first real-model target because each task maps to ordinary web-app files and tests.
|
The target is a compact 4chan-style imageboard: boards, threads, replies, images, tripcodes, sessions, reports, and moderation. That is larger than a toy parser, but it is a better first real-model target because failures are ordinary web-app failures: routes, schema, file handling, tests, and model helpers.
|
||||||
|
|
||||||
Keep the first run scoped to `TASK-001`. Let later tasks build on the previous completed task.
|
## What The Template Creates
|
||||||
|
|
||||||
## What You Will Build
|
Run this from a disposable parent directory:
|
||||||
|
|
||||||
You will create a disposable Flask project with SQLite and use NightShift to implement:
|
```bash
|
||||||
|
nightshift init --template imageboard --root nightshift-imageboard
|
||||||
|
cd nightshift-imageboard
|
||||||
|
```
|
||||||
|
|
||||||
1. Board and thread data model, routes, SQLite schema, and tests.
|
The template creates:
|
||||||
2. Image upload and thumbnail generation.
|
|
||||||
3. Bump ordering and reply counters.
|
|
||||||
4. Tripcodes and session cookies.
|
|
||||||
5. Moderation and report queue.
|
|
||||||
|
|
||||||
NightShift still controls the workflow. The model proposes code; NightShift validates, applies, tests, records artifacts, and shows the result in the dashboard.
|
```text
|
||||||
|
nightshift.yaml
|
||||||
|
.nightshift/
|
||||||
|
agents/
|
||||||
|
planner.md
|
||||||
|
implementer.md
|
||||||
|
reviewer.md
|
||||||
|
tasks.md
|
||||||
|
src/
|
||||||
|
imageboard/
|
||||||
|
tests/
|
||||||
|
templates/
|
||||||
|
static/
|
||||||
|
uploads/
|
||||||
|
thumbs/
|
||||||
|
```
|
||||||
|
|
||||||
|
NightShift control files live in `.nightshift/`. Application source lives under `src/`, tests under `tests/`, HTML templates under `templates/`, and image/static files under `static/`.
|
||||||
|
|
||||||
|
The template does not create the Flask app, schema, models, or tests. That is intentional: `TASK-001` asks the agent to create the initial application slice. This is a better exercise of NightShift than pre-building the app and asking for a small patch.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
|
|
@ -26,7 +44,7 @@ Install NightShift from this repository:
|
||||||
python -m pip install -e .
|
python -m pip install -e .
|
||||||
```
|
```
|
||||||
|
|
||||||
Install runtime dependencies for the target project:
|
Install target project dependencies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m pip install flask pillow pytest
|
python -m pip install flask pillow pytest
|
||||||
|
|
@ -41,371 +59,65 @@ ollama list
|
||||||
|
|
||||||
NightShift uses Ollama's local HTTP API, normally at `http://localhost:11434`.
|
NightShift uses Ollama's local HTTP API, normally at `http://localhost:11434`.
|
||||||
|
|
||||||
## 1. Create A Scratch Target Project
|
## Task Plan
|
||||||
|
|
||||||
Do not run apply-mode experiments directly inside the NightShift repo. Create a disposable project.
|
The template writes this task sequence to `.nightshift/tasks.md`.
|
||||||
|
|
||||||
PowerShell:
|
1. Board and thread foundation
|
||||||
|
Implement the initial data model, SQLite schema, model helpers, `/board/<name>` and `/thread/<id>` routes, and tests.
|
||||||
|
|
||||||
```powershell
|
2. Image upload and thumbnails
|
||||||
$TargetProject = "$HOME\Documents\nightshift-imageboard"
|
Add image attachments, metadata, upload storage under `static/uploads`, and thumbnails under `static/thumbs`.
|
||||||
New-Item -ItemType Directory -Force $TargetProject
|
|
||||||
Set-Location $TargetProject
|
|
||||||
New-Item -ItemType Directory -Force agents, tests, static\uploads, static\thumbs, templates
|
|
||||||
```
|
|
||||||
|
|
||||||
Bash:
|
3. Bump ordering and reply counts
|
||||||
|
Sort board threads by most recent reply and maintain reply counters.
|
||||||
|
|
||||||
```bash
|
4. Tripcodes and session cookies
|
||||||
mkdir -p ~/nightshift-imageboard/{agents,tests,static/uploads,static/thumbs,templates}
|
Add anonymous names, optional tripcodes, and lightweight poster identity.
|
||||||
cd ~/nightshift-imageboard
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2. Add The Starter App
|
5. Moderation and report queue
|
||||||
|
Add reporting, report queue, dismiss actions, and post hiding.
|
||||||
|
|
||||||
Create `app.py`:
|
Run only `TASK-001` first. Do not ask the model to implement uploads, tripcodes, or moderation until the previous tasks pass.
|
||||||
|
|
||||||
```python
|
## Validate And Run
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
from flask import Flask, abort, g, redirect, render_template_string, request, url_for
|
|
||||||
|
|
||||||
|
|
||||||
DATABASE = "imageboard.db"
|
|
||||||
|
|
||||||
|
|
||||||
def create_app(database: str | None = None) -> Flask:
|
|
||||||
app = Flask(__name__)
|
|
||||||
app.config["DATABASE"] = database or DATABASE
|
|
||||||
app.config["UPLOAD_DIR"] = Path("static/uploads")
|
|
||||||
app.config["THUMB_DIR"] = Path("static/thumbs")
|
|
||||||
app.secret_key = "dev-secret"
|
|
||||||
|
|
||||||
@app.before_request
|
|
||||||
def open_db() -> None:
|
|
||||||
g.db = sqlite3.connect(app.config["DATABASE"])
|
|
||||||
g.db.row_factory = sqlite3.Row
|
|
||||||
|
|
||||||
@app.teardown_request
|
|
||||||
def close_db(_exc: BaseException | None) -> None:
|
|
||||||
db = g.pop("db", None)
|
|
||||||
if db is not None:
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
@app.get("/")
|
|
||||||
def index():
|
|
||||||
return redirect(url_for("board", name="test"))
|
|
||||||
|
|
||||||
@app.get("/board/<name>")
|
|
||||||
def board(name: str):
|
|
||||||
abort(501)
|
|
||||||
|
|
||||||
@app.get("/thread/<int:thread_id>")
|
|
||||||
def thread(thread_id: int):
|
|
||||||
abort(501)
|
|
||||||
|
|
||||||
return app
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
create_app().run(debug=True)
|
|
||||||
```
|
|
||||||
|
|
||||||
Create `schema.sql`:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- NightShift will fill this in during TASK-001.
|
|
||||||
```
|
|
||||||
|
|
||||||
Create `models.py`:
|
|
||||||
|
|
||||||
```python
|
|
||||||
"""Database helpers for the imageboard tutorial."""
|
|
||||||
```
|
|
||||||
|
|
||||||
Create `tests/test_app.py`:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from app import create_app
|
|
||||||
|
|
||||||
|
|
||||||
def test_index_redirects_to_test_board(tmp_path):
|
|
||||||
app = create_app(str(tmp_path / "test.db"))
|
|
||||||
client = app.test_client()
|
|
||||||
|
|
||||||
response = client.get("/")
|
|
||||||
|
|
||||||
assert response.status_code == 302
|
|
||||||
assert response.headers["Location"].endswith("/board/test")
|
|
||||||
```
|
|
||||||
|
|
||||||
## 3. Add NightShift Config
|
|
||||||
|
|
||||||
Create `nightshift.yaml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
project:
|
|
||||||
name: imageboard
|
|
||||||
root: .
|
|
||||||
task_file: tasks.md
|
|
||||||
artifact_dir: .nightshift
|
|
||||||
|
|
||||||
safety:
|
|
||||||
require_clean_worktree: false
|
|
||||||
scoped_paths:
|
|
||||||
- .
|
|
||||||
allowed_commands:
|
|
||||||
- python -m pytest -q
|
|
||||||
forbidden_commands:
|
|
||||||
- rm -rf
|
|
||||||
- git push
|
|
||||||
- curl | bash
|
|
||||||
|
|
||||||
experiment:
|
|
||||||
label: imageboard-real-model
|
|
||||||
prompt_variant: ollama-qwen25-coder-14b-v1
|
|
||||||
|
|
||||||
agents:
|
|
||||||
planner:
|
|
||||||
backend: ollama
|
|
||||||
model: qwen2.5-coder:14b
|
|
||||||
temperature: 0.2
|
|
||||||
system_prompt: agents/planner.md
|
|
||||||
|
|
||||||
implementer:
|
|
||||||
backend: ollama
|
|
||||||
model: qwen2.5-coder:14b
|
|
||||||
temperature: 0.1
|
|
||||||
system_prompt: agents/implementer.md
|
|
||||||
|
|
||||||
reviewer:
|
|
||||||
backend: ollama
|
|
||||||
model: qwen2.5-coder:14b
|
|
||||||
temperature: 0.1
|
|
||||||
system_prompt: agents/reviewer.md
|
|
||||||
|
|
||||||
pipeline:
|
|
||||||
max_task_retries: 3
|
|
||||||
continue_on_task_failure: false
|
|
||||||
stages:
|
|
||||||
- id: plan
|
|
||||||
type: agent
|
|
||||||
agent: planner
|
|
||||||
output: plan.md
|
|
||||||
|
|
||||||
- id: context
|
|
||||||
type: repo_context
|
|
||||||
output: context-pack.md
|
|
||||||
|
|
||||||
- id: implement
|
|
||||||
type: file_writer
|
|
||||||
agent: implementer
|
|
||||||
output: proposed.patch
|
|
||||||
|
|
||||||
- id: normalize
|
|
||||||
type: patch_normalizer
|
|
||||||
output: normalized.patch
|
|
||||||
|
|
||||||
- id: validate_patch
|
|
||||||
type: patch_validator
|
|
||||||
output: patch-validation.md
|
|
||||||
max_files: 8
|
|
||||||
max_lines: 700
|
|
||||||
on_fail: implement
|
|
||||||
|
|
||||||
- id: apply_patch
|
|
||||||
type: patch_apply
|
|
||||||
mode: apply
|
|
||||||
output: patch-apply-output.txt
|
|
||||||
on_fail: implement
|
|
||||||
|
|
||||||
- id: test
|
|
||||||
type: command
|
|
||||||
commands:
|
|
||||||
- python -m pytest -q
|
|
||||||
output: test-output.txt
|
|
||||||
shell: true
|
|
||||||
timeout_seconds: 20
|
|
||||||
on_fail: implement
|
|
||||||
|
|
||||||
- id: review
|
|
||||||
type: agent_review
|
|
||||||
agent: reviewer
|
|
||||||
on_fail: implement
|
|
||||||
output: review.md
|
|
||||||
|
|
||||||
- id: summarize
|
|
||||||
type: summarize
|
|
||||||
output: final-notes.md
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4. Add Agent Prompts
|
|
||||||
|
|
||||||
Create `agents/planner.md`:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
You are the planning agent for NightShift.
|
|
||||||
|
|
||||||
Create a concise implementation plan for the current task.
|
|
||||||
|
|
||||||
If you need repository context before planning, output lookup requests exactly like this:
|
|
||||||
|
|
||||||
lookup_requests:
|
|
||||||
- tool: read_file
|
|
||||||
path: relative/path.py
|
|
||||||
- tool: grep
|
|
||||||
path: .
|
|
||||||
pattern: search_regex
|
|
||||||
|
|
||||||
After context is provided, write a short plan with:
|
|
||||||
- files to edit
|
|
||||||
- tests to add or update
|
|
||||||
- risks
|
|
||||||
|
|
||||||
Do not write code.
|
|
||||||
```
|
|
||||||
|
|
||||||
Create `agents/implementer.md`:
|
|
||||||
|
|
||||||
````markdown
|
|
||||||
You are the implementation agent for NightShift.
|
|
||||||
|
|
||||||
Output only complete file content blocks.
|
|
||||||
Use one fenced block per file with this exact opening form:
|
|
||||||
```file:relative/path.py
|
|
||||||
<complete file content>
|
|
||||||
```
|
|
||||||
Do not include explanations before or after the file blocks.
|
|
||||||
Include tests when needed.
|
|
||||||
Keep the change as small as possible.
|
|
||||||
Only edit files needed for the task.
|
|
||||||
````
|
|
||||||
|
|
||||||
Create `agents/reviewer.md`:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
You are the review agent for NightShift.
|
|
||||||
|
|
||||||
Review the task, plan, patch artifacts, test output, and final state.
|
|
||||||
|
|
||||||
Output exactly:
|
|
||||||
|
|
||||||
status: pass | fail | retry | escalate
|
|
||||||
reason: <short explanation>
|
|
||||||
next_stage: <optional stage id>
|
|
||||||
context_update: <compact useful note>
|
|
||||||
|
|
||||||
Use retry when the implementation is close but needs another patch.
|
|
||||||
Use fail when the patch is unsafe, unrelated, or clearly broken.
|
|
||||||
Use pass only when the acceptance criteria are satisfied.
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5. Add The Task List
|
|
||||||
|
|
||||||
Create `tasks.md`:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# Tasks
|
|
||||||
|
|
||||||
- [ ] TASK-001: Board and thread foundation
|
|
||||||
|
|
||||||
Description:
|
|
||||||
Implement the initial imageboard data model and read routes. Add a SQLite schema and model helpers for boards, threads, and replies. Implement `/board/<name>` and `/thread/<id>` routes with simple HTML responses. Include tests that initialize a temporary database, create board/thread/reply records, and verify both routes.
|
|
||||||
|
|
||||||
Acceptance Criteria:
|
|
||||||
- Defines SQLite tables for boards, threads, and replies
|
|
||||||
- Provides database initialization and model helper functions
|
|
||||||
- Implements `/board/<name>` route showing threads for that board
|
|
||||||
- Implements `/thread/<id>` route showing the thread and replies
|
|
||||||
- Includes route and model tests using a temporary database
|
|
||||||
|
|
||||||
- [ ] TASK-002: Image upload and thumbnails
|
|
||||||
|
|
||||||
Dependencies:
|
|
||||||
- TASK-001
|
|
||||||
|
|
||||||
Description:
|
|
||||||
Add image attachment support for new threads and replies. Store uploaded image metadata in SQLite, save uploaded files under `static/uploads`, and generate thumbnails under `static/thumbs`.
|
|
||||||
|
|
||||||
Acceptance Criteria:
|
|
||||||
- Accepts image uploads for threads and replies
|
|
||||||
- Stores image filename, thumbnail filename, MIME type, and size
|
|
||||||
- Generates thumbnails with Pillow
|
|
||||||
- Rejects unsupported or oversized files
|
|
||||||
- Includes upload and thumbnail tests
|
|
||||||
|
|
||||||
- [ ] TASK-003: Bump ordering and reply counts
|
|
||||||
|
|
||||||
Dependencies:
|
|
||||||
- TASK-002
|
|
||||||
|
|
||||||
Description:
|
|
||||||
Sort board threads by most recent bump. Creating a reply updates the thread bump timestamp and increments reply counters.
|
|
||||||
|
|
||||||
Acceptance Criteria:
|
|
||||||
- Board pages sort threads by latest bump time
|
|
||||||
- Replies increment thread reply count
|
|
||||||
- Reply creation updates bump timestamp
|
|
||||||
- Tests cover ordering and counters
|
|
||||||
|
|
||||||
- [ ] TASK-004: Tripcodes and session cookies
|
|
||||||
|
|
||||||
Dependencies:
|
|
||||||
- TASK-003
|
|
||||||
|
|
||||||
Description:
|
|
||||||
Add anonymous names, optional tripcodes, and a session cookie for lightweight poster identity.
|
|
||||||
|
|
||||||
Acceptance Criteria:
|
|
||||||
- Supports optional name and tripcode input
|
|
||||||
- Stores tripcode hashes without storing raw tripcode secrets
|
|
||||||
- Sets and reuses a poster session cookie
|
|
||||||
- Displays stable poster identity on posts
|
|
||||||
- Includes tripcode and session tests
|
|
||||||
|
|
||||||
- [ ] TASK-005: Moderation and report queue
|
|
||||||
|
|
||||||
Dependencies:
|
|
||||||
- TASK-004
|
|
||||||
|
|
||||||
Description:
|
|
||||||
Add post reporting and a simple moderation queue. Moderators can view reports, dismiss reports, and hide reported posts.
|
|
||||||
|
|
||||||
Acceptance Criteria:
|
|
||||||
- Users can report threads and replies
|
|
||||||
- Reports are stored with reason and timestamp
|
|
||||||
- Moderation queue lists open reports
|
|
||||||
- Moderation actions can dismiss reports or hide posts
|
|
||||||
- Includes moderation and report queue tests
|
|
||||||
```
|
|
||||||
|
|
||||||
## 6. Validate And Run
|
|
||||||
|
|
||||||
Validate the project:
|
Validate the project:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m nightshift.cli validate --config nightshift.yaml
|
nightshift validate
|
||||||
```
|
```
|
||||||
|
|
||||||
Run only the first task:
|
Run the first task:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m nightshift.cli run --config nightshift.yaml --task TASK-001
|
nightshift run --task TASK-001
|
||||||
```
|
```
|
||||||
|
|
||||||
Start the dashboard:
|
Start the dashboard:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m nightshift.cli web --config nightshift.yaml --host 127.0.0.1 --port 8765
|
nightshift web --host 127.0.0.1 --port 8765
|
||||||
```
|
```
|
||||||
|
|
||||||
Open `http://127.0.0.1:8765/`.
|
Open `http://127.0.0.1:8765/`.
|
||||||
|
|
||||||
## Notes On Scope
|
## What To Inspect
|
||||||
|
|
||||||
This is still a non-trivial first project. The advantage over a tiny interpreter is that failures are ordinary web-app failures: missing routes, schema mistakes, file handling, or tests. Those are easier to inspect in NightShift artifacts than parser recursion or tokenizer loops.
|
Useful artifacts:
|
||||||
|
|
||||||
Keep the tasks sequential. Do not ask the model to implement uploads, tripcodes, or moderation before `TASK-001` is passing.
|
```text
|
||||||
|
.nightshift/runs/<run-id>/devlog.md
|
||||||
|
.nightshift/runs/<run-id>/run.log
|
||||||
|
.nightshift/runs/<run-id>/tasks/TASK-001/plan.md
|
||||||
|
.nightshift/runs/<run-id>/tasks/TASK-001/context-pack.md
|
||||||
|
.nightshift/runs/<run-id>/tasks/TASK-001/proposed.patch
|
||||||
|
.nightshift/runs/<run-id>/tasks/TASK-001/test-output.txt
|
||||||
|
.nightshift/runs/<run-id>/tasks/TASK-001/final-notes.md
|
||||||
|
```
|
||||||
|
|
||||||
|
The dashboard surfaces `devlog.md`, run status, logs, and links to finer-grained artifacts.
|
||||||
|
|
||||||
|
## Scope Note
|
||||||
|
|
||||||
|
This project is still not tiny. The advantage over a language interpreter is that the model works in familiar web-app territory and failures are easier to inspect. If `TASK-001` fails, read `devlog.md`, `test-output.txt`, and the implementer output before increasing retries or changing prompts.
|
||||||
|
|
|
||||||
4
lisp.py
4
lisp.py
|
|
@ -1,4 +0,0 @@
|
||||||
"""Tiny Lisp quickstart target.
|
|
||||||
|
|
||||||
NightShift tasks in this example are intended to fill this module in.
|
|
||||||
"""
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
project:
|
|
||||||
name: tiny-lisp
|
|
||||||
root: .
|
|
||||||
task_file: tasks.md
|
|
||||||
artifact_dir: .nightshift
|
|
||||||
|
|
||||||
safety:
|
|
||||||
require_clean_worktree: false
|
|
||||||
scoped_paths:
|
|
||||||
- .
|
|
||||||
allowed_commands:
|
|
||||||
- python -m unittest discover -v
|
|
||||||
forbidden_commands:
|
|
||||||
- rm -rf
|
|
||||||
- git push
|
|
||||||
- curl | bash
|
|
||||||
|
|
||||||
agents:
|
|
||||||
planner:
|
|
||||||
backend: ollama
|
|
||||||
model: qwen2.5-coder:14b
|
|
||||||
temperature: 0.2
|
|
||||||
system_prompt: agents/planner.md
|
|
||||||
|
|
||||||
implementer:
|
|
||||||
backend: ollama
|
|
||||||
model: qwen2.5-coder:14b
|
|
||||||
temperature: 0.1
|
|
||||||
system_prompt: agents/implementer.md
|
|
||||||
|
|
||||||
reviewer:
|
|
||||||
backend: ollama
|
|
||||||
model: qwen2.5-coder:14b
|
|
||||||
temperature: 0.1
|
|
||||||
system_prompt: agents/reviewer.md
|
|
||||||
|
|
||||||
pipeline:
|
|
||||||
max_task_retries: 3
|
|
||||||
continue_on_task_failure: false
|
|
||||||
stages:
|
|
||||||
- id: plan
|
|
||||||
type: agent
|
|
||||||
agent: planner
|
|
||||||
output: plan.md
|
|
||||||
|
|
||||||
- id: context
|
|
||||||
type: repo_context
|
|
||||||
output: context-pack.md
|
|
||||||
|
|
||||||
- id: implement
|
|
||||||
type: file_writer
|
|
||||||
agent: implementer
|
|
||||||
output: proposed.patch
|
|
||||||
|
|
||||||
- id: normalize
|
|
||||||
type: patch_normalizer
|
|
||||||
output: normalized.patch
|
|
||||||
|
|
||||||
- id: validate_patch
|
|
||||||
type: patch_validator
|
|
||||||
output: patch-validation.md
|
|
||||||
max_files: 4
|
|
||||||
max_lines: 400
|
|
||||||
on_fail: implement
|
|
||||||
|
|
||||||
- id: apply_patch
|
|
||||||
type: patch_apply
|
|
||||||
mode: apply
|
|
||||||
output: patch-apply-output.txt
|
|
||||||
on_fail: implement
|
|
||||||
|
|
||||||
- id: test
|
|
||||||
type: command
|
|
||||||
commands:
|
|
||||||
- python -m unittest discover -v
|
|
||||||
output: test-output.txt
|
|
||||||
shell: true
|
|
||||||
timeout_seconds: 60
|
|
||||||
on_fail: implement
|
|
||||||
|
|
||||||
- id: review
|
|
||||||
type: agent_review
|
|
||||||
agent: reviewer
|
|
||||||
on_fail: implement
|
|
||||||
output: review.md
|
|
||||||
|
|
||||||
- id: summarize
|
|
||||||
type: summarize
|
|
||||||
output: final-notes.md
|
|
||||||
|
|
@ -30,6 +30,12 @@ def build_parser() -> argparse.ArgumentParser:
|
||||||
|
|
||||||
init_parser = subparsers.add_parser("init", help="Create starter NightShift files.")
|
init_parser = subparsers.add_parser("init", help="Create starter NightShift files.")
|
||||||
init_parser.add_argument("--root", default=".", help="Directory to initialize.")
|
init_parser.add_argument("--root", default=".", help="Directory to initialize.")
|
||||||
|
init_parser.add_argument(
|
||||||
|
"--template",
|
||||||
|
default="basic",
|
||||||
|
choices=("basic", "imageboard"),
|
||||||
|
help="Starter template to create.",
|
||||||
|
)
|
||||||
init_parser.add_argument("--force", action="store_true", help="Overwrite existing starter files.")
|
init_parser.add_argument("--force", action="store_true", help="Overwrite existing starter files.")
|
||||||
|
|
||||||
validate_parser = subparsers.add_parser("validate", help="Validate nightshift.yaml.")
|
validate_parser = subparsers.add_parser("validate", help="Validate nightshift.yaml.")
|
||||||
|
|
@ -57,7 +63,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if args.command == "init":
|
if args.command == "init":
|
||||||
written = init_project(Path(args.root), force=args.force)
|
written = init_project(Path(args.root), force=args.force, template=args.template)
|
||||||
print("Created NightShift starter files:")
|
print("Created NightShift starter files:")
|
||||||
for path in written:
|
for path in written:
|
||||||
print(f"- {path}")
|
print(f"- {path}")
|
||||||
|
|
|
||||||
|
|
@ -16,15 +16,38 @@ STARTER_FILES = {
|
||||||
"agents/reviewer.md": templates.REVIEWER_PROMPT,
|
"agents/reviewer.md": templates.REVIEWER_PROMPT,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IMAGEBOARD_FILES = {
|
||||||
|
"nightshift.yaml": templates.IMAGEBOARD_NIGHTSHIFT_YAML,
|
||||||
|
".nightshift/tasks.md": templates.IMAGEBOARD_TASKS_MD,
|
||||||
|
".nightshift/agents/planner.md": templates.REAL_MODEL_PLANNER_PROMPT,
|
||||||
|
".nightshift/agents/implementer.md": templates.REAL_MODEL_IMPLEMENTER_PROMPT,
|
||||||
|
".nightshift/agents/reviewer.md": templates.REAL_MODEL_REVIEWER_PROMPT,
|
||||||
|
"README.md": templates.IMAGEBOARD_README,
|
||||||
|
"src/imageboard/.gitkeep": "",
|
||||||
|
"tests/.gitkeep": "",
|
||||||
|
"templates/.gitkeep": "",
|
||||||
|
"static/uploads/.gitkeep": "",
|
||||||
|
"static/thumbs/.gitkeep": "",
|
||||||
|
}
|
||||||
|
|
||||||
def init_project(root: Path, force: bool = False) -> list[Path]:
|
PROJECT_TEMPLATES = {
|
||||||
|
"basic": STARTER_FILES,
|
||||||
|
"imageboard": IMAGEBOARD_FILES,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def init_project(root: Path, force: bool = False, template: str = "basic") -> list[Path]:
|
||||||
"""Create starter NightShift files under root.
|
"""Create starter NightShift files under root.
|
||||||
|
|
||||||
Existing files are left untouched unless force is true.
|
Existing files are left untouched unless force is true.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
root = root.resolve()
|
root = root.resolve()
|
||||||
targets = [root / relative for relative in STARTER_FILES]
|
if template not in PROJECT_TEMPLATES:
|
||||||
|
known = ", ".join(sorted(PROJECT_TEMPLATES))
|
||||||
|
raise InitError(f"Unknown template '{template}'. Available templates: {known}")
|
||||||
|
files = PROJECT_TEMPLATES[template]
|
||||||
|
targets = [root / relative for relative in files]
|
||||||
existing = [path for path in targets if path.exists()]
|
existing = [path for path in targets if path.exists()]
|
||||||
if existing and not force:
|
if existing and not force:
|
||||||
formatted = ", ".join(str(path.relative_to(root)) for path in existing)
|
formatted = ", ".join(str(path.relative_to(root)) for path in existing)
|
||||||
|
|
@ -34,7 +57,7 @@ def init_project(root: Path, force: bool = False) -> list[Path]:
|
||||||
)
|
)
|
||||||
|
|
||||||
written: list[Path] = []
|
written: list[Path] = []
|
||||||
for relative, content in STARTER_FILES.items():
|
for relative, content in files.items():
|
||||||
path = root / relative
|
path = root / relative
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
path.write_text(content, encoding="utf-8")
|
path.write_text(content, encoding="utf-8")
|
||||||
|
|
|
||||||
|
|
@ -122,3 +122,256 @@ reason: <short explanation>
|
||||||
next_stage: <optional stage id>
|
next_stage: <optional stage id>
|
||||||
context_update: <compact useful note>
|
context_update: <compact useful note>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
IMAGEBOARD_NIGHTSHIFT_YAML = """project:
|
||||||
|
name: imageboard
|
||||||
|
root: .
|
||||||
|
task_file: .nightshift/tasks.md
|
||||||
|
artifact_dir: .nightshift
|
||||||
|
|
||||||
|
safety:
|
||||||
|
require_clean_worktree: false
|
||||||
|
scoped_paths:
|
||||||
|
- src
|
||||||
|
- tests
|
||||||
|
- templates
|
||||||
|
- static
|
||||||
|
- schema.sql
|
||||||
|
- pyproject.toml
|
||||||
|
allowed_commands:
|
||||||
|
- python -m pytest -q
|
||||||
|
forbidden_commands:
|
||||||
|
- rm -rf
|
||||||
|
- git push
|
||||||
|
- curl | bash
|
||||||
|
|
||||||
|
experiment:
|
||||||
|
label: imageboard-real-model
|
||||||
|
prompt_variant: ollama-qwen25-coder-14b-v1
|
||||||
|
|
||||||
|
agents:
|
||||||
|
planner:
|
||||||
|
backend: ollama
|
||||||
|
model: qwen2.5-coder:14b
|
||||||
|
temperature: 0.2
|
||||||
|
system_prompt: .nightshift/agents/planner.md
|
||||||
|
|
||||||
|
implementer:
|
||||||
|
backend: ollama
|
||||||
|
model: qwen2.5-coder:14b
|
||||||
|
temperature: 0.1
|
||||||
|
system_prompt: .nightshift/agents/implementer.md
|
||||||
|
|
||||||
|
reviewer:
|
||||||
|
backend: ollama
|
||||||
|
model: qwen2.5-coder:14b
|
||||||
|
temperature: 0.1
|
||||||
|
system_prompt: .nightshift/agents/reviewer.md
|
||||||
|
|
||||||
|
pipeline:
|
||||||
|
max_task_retries: 3
|
||||||
|
continue_on_task_failure: false
|
||||||
|
stages:
|
||||||
|
- id: plan
|
||||||
|
type: agent
|
||||||
|
agent: planner
|
||||||
|
output: plan.md
|
||||||
|
|
||||||
|
- id: context
|
||||||
|
type: repo_context
|
||||||
|
output: context-pack.md
|
||||||
|
|
||||||
|
- id: implement
|
||||||
|
type: file_writer
|
||||||
|
agent: implementer
|
||||||
|
output: proposed.patch
|
||||||
|
|
||||||
|
- id: normalize
|
||||||
|
type: patch_normalizer
|
||||||
|
output: normalized.patch
|
||||||
|
|
||||||
|
- id: validate_patch
|
||||||
|
type: patch_validator
|
||||||
|
output: patch-validation.md
|
||||||
|
max_files: 10
|
||||||
|
max_lines: 900
|
||||||
|
on_fail: implement
|
||||||
|
|
||||||
|
- id: apply_patch
|
||||||
|
type: patch_apply
|
||||||
|
mode: apply
|
||||||
|
output: patch-apply-output.txt
|
||||||
|
on_fail: implement
|
||||||
|
|
||||||
|
- id: test
|
||||||
|
type: command
|
||||||
|
commands:
|
||||||
|
- python -m pytest -q
|
||||||
|
output: test-output.txt
|
||||||
|
shell: true
|
||||||
|
timeout_seconds: 20
|
||||||
|
on_fail: implement
|
||||||
|
|
||||||
|
- id: review
|
||||||
|
type: agent_review
|
||||||
|
agent: reviewer
|
||||||
|
on_fail: implement
|
||||||
|
output: review.md
|
||||||
|
|
||||||
|
- id: summarize
|
||||||
|
type: summarize
|
||||||
|
output: final-notes.md
|
||||||
|
"""
|
||||||
|
|
||||||
|
IMAGEBOARD_TASKS_MD = """# Tasks
|
||||||
|
|
||||||
|
- [ ] TASK-001: Board and thread foundation
|
||||||
|
|
||||||
|
Description:
|
||||||
|
Create the initial Flask imageboard application. Implement the board and thread data model, SQLite schema, model helpers, `/board/<name>` and `/thread/<id>` routes, and tests. Keep source code under `src/`, tests under `tests/`, HTML templates under `templates/`, and static files under `static/`.
|
||||||
|
|
||||||
|
Acceptance Criteria:
|
||||||
|
- Defines SQLite tables for boards, threads, and replies
|
||||||
|
- Provides database initialization and model helper functions
|
||||||
|
- Implements `/board/<name>` route showing threads for that board
|
||||||
|
- Implements `/thread/<id>` route showing the thread and replies
|
||||||
|
- Includes route and model tests using a temporary database
|
||||||
|
|
||||||
|
- [ ] TASK-002: Image upload and thumbnails
|
||||||
|
|
||||||
|
Dependencies:
|
||||||
|
- TASK-001
|
||||||
|
|
||||||
|
Description:
|
||||||
|
Add image attachment support for new threads and replies. Store uploaded image metadata in SQLite, save uploaded files under `static/uploads`, and generate thumbnails under `static/thumbs`.
|
||||||
|
|
||||||
|
Acceptance Criteria:
|
||||||
|
- Accepts image uploads for threads and replies
|
||||||
|
- Stores image filename, thumbnail filename, MIME type, and size
|
||||||
|
- Generates thumbnails with Pillow
|
||||||
|
- Rejects unsupported or oversized files
|
||||||
|
- Includes upload and thumbnail tests
|
||||||
|
|
||||||
|
- [ ] TASK-003: Bump ordering and reply counts
|
||||||
|
|
||||||
|
Dependencies:
|
||||||
|
- TASK-002
|
||||||
|
|
||||||
|
Description:
|
||||||
|
Sort board threads by most recent bump. Creating a reply updates the thread bump timestamp and increments reply counters.
|
||||||
|
|
||||||
|
Acceptance Criteria:
|
||||||
|
- Board pages sort threads by latest bump time
|
||||||
|
- Replies increment thread reply count
|
||||||
|
- Reply creation updates bump timestamp
|
||||||
|
- Tests cover ordering and counters
|
||||||
|
|
||||||
|
- [ ] TASK-004: Tripcodes and session cookies
|
||||||
|
|
||||||
|
Dependencies:
|
||||||
|
- TASK-003
|
||||||
|
|
||||||
|
Description:
|
||||||
|
Add anonymous names, optional tripcodes, and a session cookie for lightweight poster identity.
|
||||||
|
|
||||||
|
Acceptance Criteria:
|
||||||
|
- Supports optional name and tripcode input
|
||||||
|
- Stores tripcode hashes without storing raw tripcode secrets
|
||||||
|
- Sets and reuses a poster session cookie
|
||||||
|
- Displays stable poster identity on posts
|
||||||
|
- Includes tripcode and session tests
|
||||||
|
|
||||||
|
- [ ] TASK-005: Moderation and report queue
|
||||||
|
|
||||||
|
Dependencies:
|
||||||
|
- TASK-004
|
||||||
|
|
||||||
|
Description:
|
||||||
|
Add post reporting and a simple moderation queue. Moderators can view reports, dismiss reports, and hide reported posts.
|
||||||
|
|
||||||
|
Acceptance Criteria:
|
||||||
|
- Users can report threads and replies
|
||||||
|
- Reports are stored with reason and timestamp
|
||||||
|
- Moderation queue lists open reports
|
||||||
|
- Moderation actions can dismiss reports or hide posts
|
||||||
|
- Includes moderation and report queue tests
|
||||||
|
"""
|
||||||
|
|
||||||
|
REAL_MODEL_PLANNER_PROMPT = """You are the planning agent for NightShift.
|
||||||
|
|
||||||
|
Create a concise implementation plan for the current task.
|
||||||
|
|
||||||
|
If you need repository context before planning, output lookup requests exactly like this:
|
||||||
|
|
||||||
|
lookup_requests:
|
||||||
|
- tool: read_file
|
||||||
|
path: relative/path.py
|
||||||
|
- tool: grep
|
||||||
|
path: .
|
||||||
|
pattern: search_regex
|
||||||
|
|
||||||
|
After context is provided, write a short plan with:
|
||||||
|
- files to edit
|
||||||
|
- tests to add or update
|
||||||
|
- risks
|
||||||
|
|
||||||
|
Do not write code.
|
||||||
|
"""
|
||||||
|
|
||||||
|
REAL_MODEL_IMPLEMENTER_PROMPT = """You are the implementation agent for NightShift.
|
||||||
|
|
||||||
|
Output only complete file content blocks.
|
||||||
|
Use one fenced block per file with this exact opening form:
|
||||||
|
```file:relative/path.py
|
||||||
|
<complete file content>
|
||||||
|
```
|
||||||
|
Do not include explanations before or after the file blocks.
|
||||||
|
Include tests when needed.
|
||||||
|
Keep the change as small as possible.
|
||||||
|
Only edit files needed for the task.
|
||||||
|
"""
|
||||||
|
|
||||||
|
REAL_MODEL_REVIEWER_PROMPT = """You are the review agent for NightShift.
|
||||||
|
|
||||||
|
Review the task, plan, patch artifacts, test output, and final state.
|
||||||
|
|
||||||
|
Output exactly:
|
||||||
|
|
||||||
|
status: pass | fail | retry | escalate
|
||||||
|
reason: <short explanation>
|
||||||
|
next_stage: <optional stage id>
|
||||||
|
context_update: <compact useful note>
|
||||||
|
|
||||||
|
Use retry when the implementation is close but needs another patch.
|
||||||
|
Use fail when the patch is unsafe, unrelated, or clearly broken.
|
||||||
|
Use pass only when the acceptance criteria are satisfied.
|
||||||
|
"""
|
||||||
|
|
||||||
|
IMAGEBOARD_README = """# NightShift Imageboard Target
|
||||||
|
|
||||||
|
This project was created with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nightshift init --template imageboard
|
||||||
|
```
|
||||||
|
|
||||||
|
NightShift control files live in `.nightshift/`. Target application code should live under `src/`, tests under `tests/`, templates under `templates/`, and uploaded/generated static files under `static/`.
|
||||||
|
|
||||||
|
Install target dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m pip install flask pillow pytest
|
||||||
|
```
|
||||||
|
|
||||||
|
Validate the project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nightshift validate
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the first task:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nightshift run --task TASK-001
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
|
||||||
52
tasks.md
52
tasks.md
|
|
@ -1,52 +0,0 @@
|
||||||
# Tasks
|
|
||||||
|
|
||||||
- [ ] TASK-001: Parse Lisp expressions
|
|
||||||
|
|
||||||
Description:
|
|
||||||
Implement tokenization and parsing for a tiny Lisp subset.
|
|
||||||
|
|
||||||
Acceptance Criteria:
|
|
||||||
- Parses numbers
|
|
||||||
- Parses symbols
|
|
||||||
- Parses nested lists
|
|
||||||
- Raises useful errors for unbalanced parentheses
|
|
||||||
- Includes unit tests
|
|
||||||
|
|
||||||
- [ ] TASK-002: Evaluate arithmetic forms
|
|
||||||
|
|
||||||
Dependencies:
|
|
||||||
- TASK-001
|
|
||||||
|
|
||||||
Description:
|
|
||||||
Evaluate parsed arithmetic expressions.
|
|
||||||
|
|
||||||
Acceptance Criteria:
|
|
||||||
- Supports `+`, `-`, `*`, and `/`
|
|
||||||
- Evaluates nested arithmetic
|
|
||||||
- Includes unit tests
|
|
||||||
|
|
||||||
- [ ] TASK-003: Add variables and definitions
|
|
||||||
|
|
||||||
Dependencies:
|
|
||||||
- TASK-002
|
|
||||||
|
|
||||||
Description:
|
|
||||||
Add an environment and support variable lookup and definitions.
|
|
||||||
|
|
||||||
Acceptance Criteria:
|
|
||||||
- Supports symbol lookup
|
|
||||||
- Supports `(define name value)`
|
|
||||||
- Keeps environment behavior tested
|
|
||||||
|
|
||||||
- [ ] TASK-004: Add conditionals
|
|
||||||
|
|
||||||
Dependencies:
|
|
||||||
- TASK-003
|
|
||||||
|
|
||||||
Description:
|
|
||||||
Implement simple truthiness and `if` expressions.
|
|
||||||
|
|
||||||
Acceptance Criteria:
|
|
||||||
- Supports `(if condition then else)`
|
|
||||||
- Handles false-like values consistently
|
|
||||||
- Includes tests for both branches
|
|
||||||
|
|
@ -38,6 +38,27 @@ class InitProjectTests(unittest.TestCase):
|
||||||
|
|
||||||
self.assertIn("TASK-001", (root / "tasks.md").read_text(encoding="utf-8"))
|
self.assertIn("TASK-001", (root / "tasks.md").read_text(encoding="utf-8"))
|
||||||
|
|
||||||
|
def test_init_imageboard_template_creates_control_and_source_dirs(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
|
||||||
|
written = init_project(root, template="imageboard")
|
||||||
|
|
||||||
|
self.assertIn(root / "nightshift.yaml", written)
|
||||||
|
self.assertTrue((root / ".nightshift" / "tasks.md").exists())
|
||||||
|
self.assertTrue((root / ".nightshift" / "agents" / "planner.md").exists())
|
||||||
|
self.assertTrue((root / "src" / "imageboard" / ".gitkeep").exists())
|
||||||
|
self.assertTrue((root / "tests" / ".gitkeep").exists())
|
||||||
|
self.assertIn(
|
||||||
|
"task_file: .nightshift/tasks.md",
|
||||||
|
(root / "nightshift.yaml").read_text(encoding="utf-8"),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_init_rejects_unknown_template(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
with self.assertRaisesRegex(InitError, "Unknown template"):
|
||||||
|
init_project(Path(directory), template="missing")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user